Compare commits

..

320 Commits

Author SHA1 Message Date
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
d3e71b6a7e Merge pull request #1687 from sbwalker/dev
changes to build/publish params for WebAssembly
2021-09-24 17:00:39 -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
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
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
8069d838d5 Merge pull request #4 from oqtane/master
sync
2021-04-20 08:50:44 -04: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
8da55e1926 Merge pull request #3 from oqtane/dev
sync
2021-04-01 19:31:21 -04: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
383 changed files with 23446 additions and 18196 deletions

View File

@ -1,6 +1,6 @@
MIT License 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@ -1,4 +1,6 @@
@inject IInstallationService InstallationService @inject IInstallationService InstallationService
@inject IJSRuntime JSRuntime
@inject SiteState SiteState
@if (_initialized) @if (_initialized)
{ {
@ -10,11 +12,13 @@
{ {
@if (string.IsNullOrEmpty(_installation.Message)) @if (string.IsNullOrEmpty(_installation.Message))
{ {
<div style="@_display">
<CascadingAuthenticationState> <CascadingAuthenticationState>
<CascadingValue Value="@PageState"> <CascadingValue Value="@PageState">
<SiteRouter OnStateChange="@ChangeState" /> <SiteRouter Runtime="@Runtime" RenderMode="@RenderMode" OnStateChange="@ChangeState" />
</CascadingValue> </CascadingValue>
</CascadingAuthenticationState> </CascadingAuthenticationState>
</div>
} }
else else
{ {
@ -26,17 +30,54 @@
} }
@code { @code {
private Installation _installation; [Parameter]
private bool _initialized; 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; } private PageState PageState { get; set; }
protected override async Task OnParametersSetAsync() protected override async Task OnParametersSetAsync()
{ {
_installation = await InstallationService.IsInstalled(); _installation = await InstallationService.IsInstalled();
if (_installation.Alias != null)
{
SiteState.Alias = _installation.Alias;
SiteState.AntiForgeryToken = AntiForgeryToken;
}
else
{
_installation.Message = "Site Not Configured Correctly - No Matching Alias Exists For Host Name";
}
_initialized = true; _initialized = true;
} }
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
if (string.IsNullOrEmpty(AntiForgeryToken))
{
// parameter values are not set when running on WebAssembly (seems to be a .NET 5 bug) - need to retrieve using JSInterop
var interop = new Interop(JSRuntime);
AntiForgeryToken = await interop.GetElementByName(Constants.RequestVerificationToken);
SiteState.AntiForgeryToken = AntiForgeryToken;
Runtime = await interop.GetElementByName("app_runtime");
RenderMode = await interop.GetElementByName("app_rendermode");
}
_display = "";
StateHasChanged();
}
}
private void ChangeState(PageState pageState) private void ChangeState(PageState pageState)
{ {
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: 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

@ -1,41 +1,30 @@
@namespace Oqtane.Installer.Controls @namespace Oqtane.Installer.Controls
@implements Oqtane.Interfaces.IDatabaseConfigControl @implements Oqtane.Interfaces.IDatabaseConfigControl
@inject IStringLocalizer<Installer> Localizer
@{ <div class="row mb-1 align-items-center">
foreach (var field in _connectionStringFields) <Label Class="col-sm-3" For="server" HelpText="Enter the database server" ResourceKey="Server">Server:</Label>
{ <div class="col-sm-9">
var fieldId = field.Name.ToLowerInvariant(); <input id="server" type="text" class="form-control" @bind="@_server" />
field.Value = field.Value.Replace("{{Date}}", DateTime.UtcNow.ToString("yyyyMMddHHmm")); </div>
</div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="database" HelpText="Enter the name of the database" ResourceKey="Database">Database:</Label>
<Label For="@fieldId" HelpText="@field.HelpText" ResourceKey="@field.Name">@Localizer[$"{field.FriendlyName}:"]</Label> <div class="col-sm-9">
</td> <input id="database" type="text" class="form-control" @bind="@_database" />
<td> </div>
<input id="@fieldId" type="text" class="form-control" @bind="@field.Value" /> </div>
</td>
</tr>
}
}
@code { @code {
private readonly List<ConnectionStringField> _connectionStringFields = new() private string _server = "(LocalDb)\\MSSQLLocalDB";
{ private string _database = "Oqtane-" + DateTime.UtcNow.ToString("yyyyMMddHHmm");
new() {Name = "Server", FriendlyName = "Server", Value = "(LocalDb)\\MSSQLLocalDB", HelpText="Enter the database server"},
new() {Name = "Database", FriendlyName = "Database", Value = "Oqtane-{{Date}}", HelpText="Enter the name of the database"}
};
public string GetConnectionString() public string GetConnectionString()
{ {
var connectionString = String.Empty; var connectionString = String.Empty;
var server = _connectionStringFields[0].Value; if (!String.IsNullOrEmpty(_server) && !String.IsNullOrEmpty(_database))
var database = _connectionStringFields[1].Value;
if (!String.IsNullOrEmpty(server) && !String.IsNullOrEmpty(database))
{ {
connectionString = $"Data Source={server};AttachDbFilename=|DataDirectory|\\{database}.mdf;Initial Catalog={database};Integrated Security=SSPI;"; connectionString = $"Data Source={_server};AttachDbFilename=|DataDirectory|\\{_database}.mdf;Initial Catalog={_database};Integrated Security=SSPI;";
} }
return connectionString; return connectionString;

View File

@ -1,54 +1,58 @@
@namespace Oqtane.Installer.Controls @namespace Oqtane.Installer.Controls
@implements Oqtane.Interfaces.IDatabaseConfigControl @implements Oqtane.Interfaces.IDatabaseConfigControl
@inject IStringLocalizer<Installer> Localizer
@{ <div class="row mb-1 align-items-center">
foreach (var field in _connectionStringFields) <Label Class="col-sm-3" For="server" HelpText="Enter the database server" ResourceKey="Server">Server:</Label>
{ <div class="col-sm-9">
var fieldId = field.Name.ToLowerInvariant(); <input id="server" type="text" class="form-control" @bind="@_server" />
var fieldType = (field.Name == "Pwd") ? "password" : "text"; </div>
field.Value = field.Value.Replace("{{Date}}", DateTime.UtcNow.ToString("yyyyMMddHHmm")); </div>
<div class="row mb-1 align-items-center">
<tr> <Label Class="col-sm-3" For="port" HelpText="Enter the port used to connect to the server" ResourceKey="Port">Port:</Label>
<td> <div class="col-sm-9">
<Label For="@fieldId" HelpText="@field.HelpText" ResourceKey="@field.Name">@Localizer[$"{field.FriendlyName}:"]</Label> <input id="port" type="text" class="form-control" @bind="@_port" />
</td> </div>
<td> </div>
<input id="@fieldId" type="@fieldType" class="form-control" @bind="@field.Value" /> <div class="row mb-1 align-items-center">
</td> <Label Class="col-sm-3" For="database" HelpText="Enter the name of the database" ResourceKey="Database">Database:</Label>
</tr> <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 { @code {
private readonly List<ConnectionStringField> _connectionStringFields = new() private string _server = "127.0.0.1";
{ private string _port = "3306";
new() {Name = "Server", FriendlyName = "Server", Value = "127.0.0.1", HelpText="Enter the database server"}, private string _database = "Oqtane-" + DateTime.UtcNow.ToString("yyyyMMddHHmm");
new() {Name = "Port", FriendlyName = "Port", Value = "3306", HelpText="Enter the port used to connect to the server"}, private string _uid = String.Empty;
new() {Name = "Database", FriendlyName = "Database", Value = "Oqtane-{{Date}}", HelpText="Enter the name of the database"}, private string _pwd = String.Empty;
new() {Name = "Uid", FriendlyName = "User Id", Value = "", HelpText="Enter the username to use for the database"},
new() {Name = "Pwd", FriendlyName = "Password", Value = "", HelpText="Enter the password to use for the database"}
};
public string GetConnectionString() public string GetConnectionString()
{ {
var connectionString = String.Empty; var connectionString = String.Empty;
var server = _connectionStringFields[0].Value; if (!String.IsNullOrEmpty(_server) && !String.IsNullOrEmpty(_database) && !String.IsNullOrEmpty(_uid) && !String.IsNullOrEmpty(_pwd))
var port = _connectionStringFields[1].Value;
var database = _connectionStringFields[2].Value;
var userId = _connectionStringFields[3].Value;
var password = _connectionStringFields[4].Value;
if (!String.IsNullOrEmpty(server) && !String.IsNullOrEmpty(database) && !String.IsNullOrEmpty(userId) && !String.IsNullOrEmpty(password))
{ {
connectionString = $"Server={server};Database={database};Uid={userId};Pwd={password};"; connectionString = $"Server={_server};Database={_database};Uid={_uid};Pwd={_pwd};";
} }
if (!String.IsNullOrEmpty(port)) if (!String.IsNullOrEmpty(_port))
{ {
connectionString += $"Port={port};"; connectionString += $"Port={_port};";
} }
return connectionString; return connectionString;
} }
} }

View File

@ -1,88 +1,76 @@
@namespace Oqtane.Installer.Controls @namespace Oqtane.Installer.Controls
@implements Oqtane.Interfaces.IDatabaseConfigControl @implements Oqtane.Interfaces.IDatabaseConfigControl
@inject IStringLocalizer<Installer> Localizer @inject IStringLocalizer<PostgreSQLConfig> Localizer
@{ <div class="row mb-1 align-items-center">
foreach (var field in _connectionStringFields) <Label Class="col-sm-3" For="server" HelpText="Enter the database server" ResourceKey="Server">Server:</Label>
{ <div class="col-sm-9">
var fieldId = field.Name.ToLowerInvariant(); <input id="server" type="text" class="form-control" @bind="@_server" />
if (field.Name != "IntegratedSecurity") </div>
{ </div>
var isVisible = ""; <div class="row mb-1 align-items-center">
var fieldType = (field.Name == "Pwd") ? "password" : "text"; <Label Class="col-sm-3" For="port" HelpText="Enter the port used to connect to the server" ResourceKey="Port">Port:</Label>
if ((field.Name == "Uid" || field.Name == "Pwd") ) <div class="col-sm-9">
{ <input id="port" type="text" class="form-control" @bind="@_port" />
var intSecurityField = _connectionStringFields.Single(f => f.Name == "IntegratedSecurity"); </div>
if (intSecurityField != null) </div>
{ <div class="row mb-1 align-items-center">
isVisible = (Convert.ToBoolean(intSecurityField.Value)) ? "display: none;" : ""; <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>
field.Value = field.Value.Replace("{{Date}}", DateTime.UtcNow.ToString("yyyyMMddHHmm")); </div>
<div class="row mb-1 align-items-center">
<tr style="@isVisible"> <Label Class="col-sm-3" For="security" HelpText="Select your security method" ResourceKey="Security">Security:</Label>
<td> <div class="col-sm-9">
<Label For="@fieldId" HelpText="@field.HelpText" ResourceKey="@field.Name">@Localizer[$"{field.FriendlyName}:"]</Label> <select id="security" class="form-select custom-select" @bind="@_security">
</td> <option value="integrated" selected>@Localizer["Integrated"]</option>
<td> <option value="custom">@Localizer["Custom"]</option>
<input id="@fieldId" type="@fieldType" class="form-control" @bind="@field.Value" />
</td>
</tr>
}
else
{
<tr>
<td>
<Label For="@fieldId" HelpText="@field.HelpText" ResourceKey="@field.Name">@Localizer[$"{field.FriendlyName}:"]</Label>
</td>
<td>
<select id="@fieldId" class="custom-select" @bind="@field.Value">
<option value="true" selected>@Localizer["True"]</option>
<option value="false">@Localizer["False"]</option>
</select> </select>
</td> </div>
</tr> </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 { @code {
private readonly List<ConnectionStringField> _connectionStringFields = new() private string _server = "127.0.0.1";
{ private string _port = "5432";
new() {Name = "Server", FriendlyName = "Server", Value = "127.0.0.1", HelpText="Enter the database server"}, private string _database = "Oqtane-" + DateTime.UtcNow.ToString("yyyyMMddHHmm");
new() {Name = "Port", FriendlyName = "Port", Value = "5432", HelpText="Enter the port used to connect to the server"}, private string _security = "integrated";
new() {Name = "Database", FriendlyName = "Database", Value = "Oqtane-{{Date}}", HelpText="Enter the name of the database"}, private string _uid = String.Empty;
new() {Name = "IntegratedSecurity", FriendlyName = "Integrated Security", Value = "true", HelpText="Select if you want integrated security or not"}, private string _pwd = String.Empty;
new() {Name = "Uid", FriendlyName = "User Id", Value = "", HelpText="Enter the username to use for the database"},
new() {Name = "Pwd", FriendlyName = "Password", Value = "", HelpText="Enter the password to use for the database"}
};
public string GetConnectionString() public string GetConnectionString()
{ {
var connectionString = String.Empty; var connectionString = String.Empty;
var server = _connectionStringFields[0].Value; if (!String.IsNullOrEmpty(_server) && !String.IsNullOrEmpty(_database) && !String.IsNullOrEmpty(_port))
var port = _connectionStringFields[1].Value;
var database = _connectionStringFields[2].Value;
var integratedSecurity = Boolean.Parse(_connectionStringFields[3].Value);
var userId = _connectionStringFields[4].Value;
var password = _connectionStringFields[5].Value;
if (!String.IsNullOrEmpty(server) && !String.IsNullOrEmpty(database) && !String.IsNullOrEmpty(port))
{ {
connectionString = $"Server={server};Port={port};Database={database};"; connectionString = $"Server={_server};Port={_port};Database={_database};";
} }
if (integratedSecurity) if (_security == "integrated")
{ {
connectionString += "Integrated Security=true;"; connectionString += "Integrated Security=true;";
} }
else else
{ {
if (!String.IsNullOrEmpty(userId) && !String.IsNullOrEmpty(password)) if (!String.IsNullOrEmpty(_uid) && !String.IsNullOrEmpty(_pwd))
{ {
connectionString += $"User ID={userId};Password={password};"; connectionString += $"User ID={_uid};Password={_pwd};";
} }
else else
{ {

View File

@ -1,86 +1,69 @@
@namespace Oqtane.Installer.Controls @namespace Oqtane.Installer.Controls
@implements Oqtane.Interfaces.IDatabaseConfigControl @implements Oqtane.Interfaces.IDatabaseConfigControl
@inject IStringLocalizer<Installer> Localizer @inject IStringLocalizer<SqlServerConfig> Localizer
@{ <div class="row mb-1 align-items-center">
foreach (var field in _connectionStringFields) <Label Class="col-sm-3" For="server" HelpText="Enter the database server" ResourceKey="Server">Server:</Label>
{ <div class="col-sm-9">
var fieldId = field.Name.ToLowerInvariant(); <input id="server" type="text" class="form-control" @bind="@_server" />
if (field.Name != "IntegratedSecurity") </div>
{ </div>
var isVisible = ""; <div class="row mb-1 align-items-center">
var fieldType = (field.Name == "Pwd") ? "password" : "text"; <Label Class="col-sm-3" For="database" HelpText="Enter the name of the database" ResourceKey="Database">Database:</Label>
if ((field.Name == "Uid" || field.Name == "Pwd") ) <div class="col-sm-9">
{ <input id="database" type="text" class="form-control" @bind="@_database" />
var intSecurityField = _connectionStringFields.Single(f => f.Name == "IntegratedSecurity"); </div>
if (intSecurityField != null) </div>
{ <div class="row mb-1 align-items-center">
isVisible = (Convert.ToBoolean(intSecurityField.Value)) ? "display: none;" : ""; <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>
field.Value = field.Value.Replace("{{Date}}", DateTime.UtcNow.ToString("yyyyMMddHHmm")); <option value="custom">@Localizer["Custom"]</option>
<tr style="@isVisible">
<td>
<Label For="@fieldId" HelpText="@field.HelpText" ResourceKey="@field.Name">@Localizer[$"{field.FriendlyName}:"]</Label>
</td>
<td>
<input id="@fieldId" type="@fieldType" class="form-control" @bind="@field.Value" />
</td>
</tr>
}
else
{
<tr>
<td>
<Label For="@fieldId" HelpText="@field.HelpText" ResourceKey="@field.Name">@Localizer[$"{field.FriendlyName}:"]</Label>
</td>
<td>
<select id="@fieldId" class="custom-select" @bind="@field.Value">
<option value="true" selected>@Localizer["True"]</option>
<option value="false">@Localizer["False"]</option>
</select> </select>
</td> </div>
</tr> </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 { @code {
private readonly List<ConnectionStringField> _connectionStringFields = new() private string _server = String.Empty;
{ private string _database = "Oqtane-" + DateTime.UtcNow.ToString("yyyyMMddHHmm");
new() {Name = "Server", FriendlyName = "Server", Value = ".", HelpText="Enter the database server"}, private string _security = "integrated";
new() {Name = "Database", FriendlyName = "Database", Value = "Oqtane-{{Date}}", HelpText="Enter the name of the database"}, private string _uid = String.Empty;
new() {Name = "IntegratedSecurity", FriendlyName = "Integrated Security", Value = "true", HelpText="Select if you want integrated security or not"}, private string _pwd = String.Empty;
new() {Name = "Uid", FriendlyName = "User Id", Value = "", HelpText="Enter the username to use for the database"},
new() {Name = "Pwd", FriendlyName = "Password", Value = "", HelpText="Enter the password to use for the database"}
};
public string GetConnectionString() public string GetConnectionString()
{ {
var connectionString = String.Empty; var connectionString = String.Empty;
var server = _connectionStringFields[0].Value; if (!String.IsNullOrEmpty(_server) && !String.IsNullOrEmpty(_database))
var database = _connectionStringFields[1].Value;
var integratedSecurity = Boolean.Parse(_connectionStringFields[2].Value);
var userId = _connectionStringFields[3].Value;
var password = _connectionStringFields[4].Value;
if (!String.IsNullOrEmpty(server) && !String.IsNullOrEmpty(database))
{ {
connectionString = $"Data Source={server};Initial Catalog={database};"; connectionString = $"Data Source={_server};Initial Catalog={_database};";
} }
if (integratedSecurity) if (_security == "integrated")
{ {
connectionString += "Integrated Security=SSPI;"; connectionString += "Integrated Security=SSPI;";
} }
else else
{ {
if (!String.IsNullOrEmpty(userId) && !String.IsNullOrEmpty(password)) if (!String.IsNullOrEmpty(_uid) && !String.IsNullOrEmpty(_pwd))
{ {
connectionString += $"User ID={userId};Password={password};"; connectionString += $"User ID={_uid};Password={_pwd};";
} }
else else
{ {
@ -90,5 +73,4 @@
return connectionString; return connectionString;
} }
} }

View File

@ -1,39 +1,23 @@
@namespace Oqtane.Installer.Controls @namespace Oqtane.Installer.Controls
@implements Oqtane.Interfaces.IDatabaseConfigControl @implements Oqtane.Interfaces.IDatabaseConfigControl
@inject IStringLocalizer<Installer> Localizer
@{ <div class="row mb-1 align-items-center">
foreach (var field in _connectionStringFields) <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">
var fieldId = field.Name.ToLowerInvariant(); <input id="server" type="text" class="form-control" @bind="@_server" />
field.Value = field.Value.Replace("{{Date}}", DateTime.UtcNow.ToString("yyyyMMddHHmm")); </div>
</div>
<tr>
<td>
<Label For="@fieldId" HelpText="@field.HelpText" ResourceKey="@field.Name">@Localizer[$"{field.FriendlyName}:"]</Label>
</td>
<td>
<input id="@fieldId" type="text" class="form-control" @bind="@field.Value" />
</td>
</tr>
}
}
@code { @code {
private readonly List<ConnectionStringField> _connectionStringFields = new() private string _server = "Oqtane-" + DateTime.UtcNow.ToString("yyyyMMddHHmm") + ".db";
{
new() {Name = "Server", FriendlyName = "File Name", Value = "Oqtane-{{Date}}.db", HelpText="Enter the file name to use for the database"}
};
public string GetConnectionString() public string GetConnectionString()
{ {
var connectionstring = String.Empty; var connectionstring = String.Empty;
var server = _connectionStringFields[0].Value; if (!String.IsNullOrEmpty(_server))
if (!String.IsNullOrEmpty(server))
{ {
connectionstring = $"Data Source={server};"; connectionstring = $"Data Source={_server};";
} }
return connectionstring; return connectionstring;

View File

@ -7,83 +7,77 @@
@inject IDatabaseService DatabaseService @inject IDatabaseService DatabaseService
@inject IJSRuntime JSRuntime @inject IJSRuntime JSRuntime
@inject IStringLocalizer<Installer> Localizer @inject IStringLocalizer<Installer> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="mx-auto text-center"> <div class="mx-auto text-center">
<img src="oqtane-black.png" /> <img src="oqtane-black.png" />
<div style="font-weight: bold">@Localizer["Version:"] @Constants.Version</div> <div style="font-weight: bold">@SharedLocalizer["Version"] @Constants.Version</div>
</div> </div>
</div> </div>
<hr class="app-rule" /> <hr class="app-rule" />
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col text-center"> <div class="col text-center">
<h2>@Localizer["Database Configuration"]</h2><br /> <h2>@Localizer["DatabaseConfig"]</h2><br />
<table class="form-group" cellpadding="4" cellspacing="4" style="margin: auto;"> <div class="container">
<tbody> <div class="row mb-1 align-items-center">
<tr> <Label Class="col-sm-3" For="databasetype" HelpText="Select the type of database you wish to use" ResourceKey="DatabaseType">Database:</Label>
<td> <div class="col-sm-9">
<Label For="databasetype" HelpText="Select the type of database you wish to create" ResourceKey="DatabaseType">Database Type:</Label> <select id="databasetype" class="form-select custom-select" value="@_databaseName" @onchange="(e => DatabaseChanged(e))">
</td>
<td>
<select id="databasetype" class="custom-select" value="@_databaseName" @onchange="(e => DatabaseChanged(e))">
@if (_databases != null) @if (_databases != null)
{ {
foreach (var database in _databases) 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> <option value="@database.Name">@Localizer[@database.Name]</option>
} }
} }
}
</select> </select>
</td> </div>
</tr> </div>
@{ @{
if (_databaseConfigType != null) if (_databaseConfigType != null)
{ {
@DatabaseConfigComponent; @DatabaseConfigComponent;
} }
} }
</tbody> </div>
</table>
</div> </div>
<div class="col text-center"> <div class="col text-center">
<h2>@Localizer["Application Administrator"]</h2><br /> <h2>@Localizer["ApplicationAdmin"]</h2><br />
<table class="form-group" cellpadding="4" cellspacing="4" style="margin: auto;"> <div class="container">
<tbody> <div class="row mb-1 align-items-center">
<tr> <Label Class="col-sm-3" For="username" HelpText="Provide a username for the primary user accountt" ResourceKey="Username">Username:</Label>
<td> <div class="col-sm-9">
<Label For="username" HelpText="The username of the host user account ( this is not customizable )" ResourceKey="Username">Username:</Label> <input id="username" type="text" class="form-control" @bind="@_hostUsername" />
</td> </div>
<td> </div>
<input id="username" type="text" class="form-control" @bind="@_hostUsername" readonly /> <div class="row mb-1 align-items-center">
</td> <Label Class="col-sm-3" For="password" HelpText="Provide a password for the primary user account" ResourceKey="Password">Password:</Label>
</tr> <div class="col-sm-9">
<tr>
<td>
<Label For="password" HelpText="Provide the password for the host user account" ResourceKey="Password">Password:</Label>
</td>
<td>
<input id="password" type="password" class="form-control" @bind="@_hostPassword" /> <input id="password" type="password" class="form-control" @bind="@_hostPassword" />
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="confirm" HelpText="Please confirm the password entered above by entering it again" ResourceKey="Confirm">Confirm:</Label>
<Label For="confirm" HelpText="Please confirm the password entered above by entering it again" ResourceKey="Confirm">Confirm:</Label> <div class="col-sm-9">
</td>
<td>
<input id="confirm" type="password" class="form-control" @bind="@_confirmPassword" /> <input id="confirm" type="password" class="form-control" @bind="@_confirmPassword" />
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="email" HelpText="Provide the email address for the host user account" ResourceKey="Email">Email:</Label>
<Label For="email" HelpText="Provide the email address for the host user account" ResourceKey="Email">Email:</Label> <div class="col-sm-9">
</td>
<td>
<input type="text" class="form-control" @bind="@_hostEmail" /> <input type="text" class="form-control" @bind="@_hostEmail" />
</td> </div>
</tr> </div>
</tbody> </div>
</table>
</div> </div>
</div> </div>
<hr class="app-rule" /> <hr class="app-rule" />
@ -94,6 +88,11 @@
</div> </div>
<div class="app-progress-indicator" style="@_loadingDisplay"></div> <div class="app-progress-indicator" style="@_loadingDisplay"></div>
</div> </div>
<div class="row">
<div class="mx-auto text-center">
<input type="checkbox" @bind="@_register" /> @Localizer["Register"]
</div>
</div>
</div> </div>
@code { @code {
@ -103,10 +102,11 @@
private object _databaseConfig; private object _databaseConfig;
private RenderFragment DatabaseConfigComponent { get; set; } private RenderFragment DatabaseConfigComponent { get; set; }
private string _hostUsername = UserNames.Host; private string _hostUsername = string.Empty;
private string _hostPassword = string.Empty; private string _hostPassword = string.Empty;
private string _confirmPassword = string.Empty; private string _confirmPassword = string.Empty;
private string _hostEmail = string.Empty; private string _hostEmail = string.Empty;
private bool _register = true;
private string _message = string.Empty; private string _message = string.Empty;
private string _loadingDisplay = "display: none;"; private string _loadingDisplay = "display: none;";
@ -126,7 +126,7 @@
} }
catch catch
{ {
_message = Localizer["Error loading Database Configuration Control"]; _message = Localizer["Error.DbConfig.Load"];
} }
} }
@ -150,7 +150,8 @@
if (firstRender) if (firstRender)
{ {
var interop = new Interop(JSRuntime); var interop = new Interop(JSRuntime);
await interop.IncludeLink("app-stylesheet", "stylesheet", "https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css", "text/css", "sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T", "anonymous", ""); await interop.IncludeLink("", "stylesheet", "https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css", "text/css", "sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC", "anonymous", "");
await interop.IncludeScript("", "https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js", "sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM", "anonymous", "", "head", "");
} }
} }
@ -162,7 +163,7 @@
connectionString = databaseConfigControl.GetConnectionString(); connectionString = databaseConfigControl.GetConnectionString();
} }
if (connectionString != "" && _hostUsername != "" && _hostPassword.Length >= 6 && _hostPassword == _confirmPassword && _hostEmail != "") if (connectionString != "" && !string.IsNullOrEmpty(_hostUsername) && _hostPassword.Length >= 6 && _hostPassword == _confirmPassword && !string.IsNullOrEmpty(_hostEmail) && _hostEmail.Contains("@"))
{ {
_loadingDisplay = ""; _loadingDisplay = "";
StateHasChanged(); StateHasChanged();
@ -176,12 +177,14 @@
DatabaseType = database.DBType, DatabaseType = database.DBType,
ConnectionString = connectionString, ConnectionString = connectionString,
Aliases = uri.Authority, Aliases = uri.Authority,
HostEmail = _hostEmail, HostUsername = _hostUsername,
HostPassword = _hostPassword, HostPassword = _hostPassword,
HostName = UserNames.Host, HostEmail = _hostEmail,
HostName = _hostUsername,
TenantName = TenantNames.Master, TenantName = TenantNames.Master,
IsNewTenant = true, IsNewTenant = true,
SiteName = Constants.DefaultSite SiteName = Constants.DefaultSite,
Register = _register
}; };
var installation = await InstallationService.Install(config); var installation = await InstallationService.Install(config);
@ -197,8 +200,7 @@
} }
else else
{ {
_message = Localizer["Please Enter All Fields And Ensure Passwords Match And Are Greater Than 5 Characters In Length"]; _message = Localizer["Message.Require.DbInfo"];
} }
} }
} }

View File

@ -1,47 +0,0 @@
namespace Oqtane
{
public class SharedResources
{
public static readonly string UserLogin = "User Login";
public static readonly string UserRegistration = "User Registration";
public static readonly string PasswordReset = "Password Reset";
public static readonly string UserProfile = "User Profile";
public static readonly string AdminDashboard = "Admin Dashboard";
public static readonly string SiteSettings = "Site Settings";
public static readonly string PageManagement = "Page Management";
public static readonly string UserManagement = "User Management";
public static readonly string ProfileManagement = "Profile Management";
public static readonly string RoleManagement = "Role Management";
public static readonly string FileManagement = "File Management";
public static readonly string RecycleBin = "Recycle Bin";
public static readonly string EventLog = "Event Log";
public static readonly string SiteManagement = "Site Management";
public static readonly string ModuleManagement = "Module Management";
public static readonly string ThemeManagement = "Theme Management";
public static readonly string LanguageManagement = "Language Management";
public static readonly string ScheduledJobs = "Scheduled Jobs";
public static readonly string SqlManagement = "Sql Management";
public static readonly string SystemInfo = "System Info";
public static readonly string SystemUpdate = "System Update";
}
}

View File

@ -2,7 +2,7 @@
@inherits ModuleBase @inherits ModuleBase
@inject IPageService PageService @inject IPageService PageService
@inject IUserService UserService @inject IUserService UserService
@inject IStringLocalizer<SharedResources> Localizer @inject IStringLocalizer<SharedResources> SharedLocalizer
<div class="row"> <div class="row">
@foreach (var p in _pages) @foreach (var p in _pages)
@ -12,7 +12,7 @@
string url = NavigateUrl(p.Path); string url = NavigateUrl(p.Path);
<div class="col-md-2 mx-auto text-center"> <div class="col-md-2 mx-auto text-center">
<NavLink class="nav-link" href="@url" Match="NavLinkMatch.All"> <NavLink class="nav-link" href="@url" Match="NavLinkMatch.All">
<h2><span class="@p.Icon" aria-hidden="true"></span></h2>@Localizer[p.Name] <h2><span class="@p.Icon" aria-hidden="true"></span></h2>@SharedLocalizer[p.Name]
</NavLink> </NavLink>
</div> </div>
} }

View File

@ -11,7 +11,7 @@
Module module = await ModuleService.GetModuleAsync(ModuleState.ModuleId); Module module = await ModuleService.GetModuleAsync(ModuleState.ModuleId);
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) 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); AddModuleMessage(message, MessageType.Error);
} }

View File

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

View File

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

View File

@ -4,16 +4,16 @@
@inject IFileService FileService @inject IFileService FileService
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject IStringLocalizer<Edit> Localizer @inject IStringLocalizer<Edit> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_folders != null) @if (_folders != null)
{ {
<table class="table table-borderless"> <form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<tr> <div class="container">
<td> <div class="row mb-1 align-items-center">
<Label For="parent" HelpText="Select the parent folder" ResourceKey="Parent">Parent: </Label> <Label Class="col-sm-3" For="parent" HelpText="Select the parent folder" ResourceKey="Parent">Parent: </Label>
</td> <div class="col-sm-9">
<td> <select id="parent" class="form-select" @bind="@_parentId" required>
<select id="parent" class="form-control" @bind="@_parentId">
@if (PageState.QueryString.ContainsKey("id")) @if (PageState.QueryString.ContainsKey("id"))
{ {
<option value="-1">&lt;@Localizer["NoParent"]&gt;</option> <option value="-1">&lt;@Localizer["NoParent"]&gt;</option>
@ -23,48 +23,61 @@
<option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option> <option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option>
} }
</select> </select>
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="name" HelpText="Enter the folder name" ResourceKey="Name">Name: </Label>
<Label for="name" HelpText="Enter the folder name" ResourceKey="Name">Name: </Label> <div class="col-sm-9">
</td> <input id="name" class="form-control" @bind="@_name" maxlength="256" required />
<td> </div>
<input id="name" class="form-control" @bind="@_name" /> </div>
</td> <div class="row mb-1 align-items-center">
</tr> <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>
<tr> <div class="col-sm-9">
<td>
<Label for="type" HelpText="Select the folder type. Private folders are only accessible by authorized users. Public folders can be accessed by all users" ResourceKey="Name">Type: </Label>
</td>
<td>
@if (PageState.QueryString.ContainsKey("id")) @if (PageState.QueryString.ContainsKey("id"))
{ {
<input id="type" class="form-control" readonly @bind="@_type" /> <input id="type" class="form-control" readonly @bind="@_type" />
} }
else else
{ {
<select id="type" class="form-control" @bind="@_type"> <select id="type" class="form-select" @bind="@_type" required>
<option value="@FolderTypes.Private">@Localizer[FolderTypes.Private]</option> <option value="@FolderTypes.Private">@Localizer[FolderTypes.Private]</option>
<option value="@FolderTypes.Public">@Localizer[FolderTypes.Public]</option> <option value="@FolderTypes.Public">@Localizer[FolderTypes.Public]</option>
</select> </select>
} }
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td colspan="2" align="center"> <Label Class="col-sm-3" For="imagesizes" HelpText="Enter a list of image sizes which can be generated dynamically from uploaded images (ie. 200x200,x200,200x)" ResourceKey="ImageSizes">Image Sizes: </Label>
<Label For="permissions" HelpText="Select the permissions you want for the folder" ResourceKey="Permissions">Permissions: </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" /> <PermissionGrid EntityName="@EntityNames.Folder" PermissionNames="@(PermissionNames.Browse + "," + PermissionNames.View + "," + PermissionNames.Edit)" Permissions="@_permissions" @ref="_permissionGrid" />
</td>
</tr> </div>
</table> </div>
</div>
</form>
@if (!_isSystem) @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")) @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" /> <ActionDialog Header="Delete Folder" Message="Are You Sure You Wish To Delete This Folder?" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteFolder())" ResourceKey="DeleteFolder" />
} }
<br /> <br />
@ -76,11 +89,15 @@
} }
@code { @code {
private ElementReference form;
private bool validated = false;
private List<Folder> _folders; private List<Folder> _folders;
private int _folderId = -1; private int _folderId = -1;
private int _parentId = -1; private int _parentId = -1;
private string _name; private string _name;
private string _type = FolderTypes.Private; private string _type = FolderTypes.Private;
private string _imagesizes = string.Empty;
private string _capacity = "0";
private bool _isSystem; private bool _isSystem;
private string _permissions = string.Empty; private string _permissions = string.Empty;
private string _createdBy; private string _createdBy;
@ -111,6 +128,8 @@
_parentId = folder.ParentId ?? -1; _parentId = folder.ParentId ?? -1;
_name = folder.Name; _name = folder.Name;
_type = folder.Type; _type = folder.Type;
_imagesizes = folder.ImageSizes;
_capacity = folder.Capacity.ToString();
_isSystem = folder.IsSystem; _isSystem = folder.IsSystem;
_permissions = folder.Permissions; _permissions = folder.Permissions;
_createdBy = folder.CreatedBy; _createdBy = folder.CreatedBy;
@ -122,27 +141,30 @@
else else
{ {
_parentId = _folders[0].FolderId; _parentId = _folders[0].FolderId;
_permissions = string.Empty;
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Loading Folder {FolderId} {Error}", _folderId, ex.Message); 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() private async Task SaveFolder()
{
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{ {
if (_name == string.Empty || _parentId == -1) if (_name == string.Empty || _parentId == -1)
{ {
AddModuleMessage(Localizer["Folders Must Have A Parent And A Name"], MessageType.Warning); AddModuleMessage(Localizer["Message.Required.FolderParent"], MessageType.Warning);
return; return;
} }
if (!_name.IsPathOrFileValid()) if (!_name.IsPathOrFileValid())
{ {
AddModuleMessage(Localizer["Folder Name Not Valid."], MessageType.Warning); AddModuleMessage(Localizer["Message.Folder.InvalidName"], MessageType.Warning);
return; return;
} }
@ -171,6 +193,8 @@
folder.Name = _name; folder.Name = _name;
folder.Type = _type; folder.Type = _type;
folder.ImageSizes = _imagesizes;
folder.Capacity = int.Parse(_capacity);
folder.IsSystem = _isSystem; folder.IsSystem = _isSystem;
folder.Permissions = _permissionGrid.GetPermissions(); folder.Permissions = _permissionGrid.GetPermissions();
@ -191,13 +215,18 @@
} }
else else
{ {
AddModuleMessage(Localizer["An Error Was Encountered Saving The Folder"], MessageType.Error); AddModuleMessage(Localizer["Error.Folder.Save"], MessageType.Error);
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Saving Folder {FolderId} {Error}", _folderId, ex.Message); await logger.LogError(ex, "Error Saving Folder {FolderId} {Error}", _folderId, ex.Message);
AddModuleMessage(Localizer["Error Saving Folder"], MessageType.Error); AddModuleMessage(Localizer["Error.Folder.Save"], MessageType.Error);
}
}
else
{
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
} }
} }
@ -225,18 +254,18 @@
} }
else else
{ {
AddModuleMessage(Localizer["Folder Has Files And Cannot Be Deleted"], MessageType.Warning); AddModuleMessage(Localizer["Message.Folder.Files.InvalidDelete"], MessageType.Warning);
} }
} }
else else
{ {
AddModuleMessage(Localizer["Folder Has Subfolders And Cannot Be Deleted"], MessageType.Warning); AddModuleMessage(Localizer["Message.Folder.Subfolders.InvalidDelete"], MessageType.Warning);
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Deleting Folder {Folder} {Error}", _folderId, ex.Message); 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 IFolderService FolderService
@inject IFileService FileService @inject IFileService FileService
@inject IStringLocalizer<Index> Localizer @inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_files != null) @if (_files != null)
{ {
<table class="table table-borderless"> <div class="container">
<tr> <div class="row mb-1 align-items-center">
<td> <div class="col-sm-2">
<label class="control-label">@Localizer["Folder:"] </label> <label class="control-label">@Localizer["Folder"] </label>
</td> </div>
<td> <div class="col-sm-6">
<select class="form-control" @onchange="(e => FolderChanged(e))"> <select class="form-select" @onchange="(e => FolderChanged(e))">
@foreach (Folder folder in _folders) @foreach (Folder folder in _folders)
{ {
<option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option> <option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option>
} }
</select> </select>
</td> </div>
<td> <div class="col-sm-4">
<ActionLink Action="Edit" Text="Edit Folder" Class="btn btn-secondary" Parameters="@($"id=" + _folderId.ToString())" ResourceKey="EditFolder" />&nbsp; <ActionLink Action="Edit" Text="Edit Folder" Class="btn btn-secondary" Parameters="@($"id=" + _folderId.ToString())" ResourceKey="EditFolder" />&nbsp;
<ActionLink Action="Edit" Text="Add Folder" Class="btn btn-secondary" ResourceKey="AddFolder" />&nbsp; <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" /> <ActionLink Action="Add" Text="Upload Files" Parameters="@($"id=" + _folderId.ToString())" ResourceKey="UploadFiles" />
</td> </div>
</tr> </div>
</table> </div>
<Pager Items="@_files"> <Pager Items="@_files">
<Header> <Header>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th>@Localizer["Name"]</th> <th>@SharedLocalizer["Name"]</th>
<th>@Localizer["Modified"]</th> <th>@Localizer["Modified"]</th>
<th>@Localizer["Type"]</th> <th>@Localizer["Type"]</th>
<th>@Localizer["Size"]</th> <th>@Localizer["Size"]</th>
</Header> </Header>
<Row> <Row>
<td><ActionLink Action="Details" Text="Edit" Parameters="@($"id=" + context.FileId.ToString())" ResourceKey="Details" /></td> <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><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><a href="@context.Url" target="_new">@context.Name</a></td>
<td>@context.ModifiedOn</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> <td>@string.Format("{0:0.00}", ((decimal)context.Size / 1000)) KB</td>
</Row> </Row>
</Pager> </Pager>
@if (_files.Count == 0) @if (_files.Count == 0)
{ {
<div class="text-center">@Localizer["No Files Exist In Selected Folder"]</div> <div class="text-center">@Localizer["NoFiles"]</div>
} }
} }
@ -73,7 +74,7 @@
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Loading Files {Error}", ex.Message); await logger.LogError(ex, "Error Loading Files {Error}", ex.Message);
AddModuleMessage(Localizer["Error Loading Files"], MessageType.Error); AddModuleMessage(Localizer["Error.File.Load"], MessageType.Error);
} }
} }
@ -93,7 +94,7 @@
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Loading Files {Error}", ex.Message); await logger.LogError(ex, "Error Loading Files {Error}", ex.Message);
AddModuleMessage(Localizer["Error Loading Files"], MessageType.Error); AddModuleMessage(Localizer["Error.File.Load"], MessageType.Error);
} }
} }
@ -103,14 +104,14 @@
{ {
await FileService.DeleteFileAsync(file.FileId); await FileService.DeleteFileAsync(file.FileId);
await logger.LogInformation("File Deleted {File}", file.Name); 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(); await GetFiles();
StateHasChanged(); StateHasChanged();
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Deleting File {File} {Error}", file.Name, ex.Message); 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 NavigationManager NavigationManager
@inject IJobService JobService @inject IJobService JobService
@inject IStringLocalizer<Edit> Localizer @inject IStringLocalizer<Edit> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<table class="table table-borderless"> <form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<tr> <div class="container">
<td> <div class="row mb-1 align-items-center">
<Label For="name" HelpText="Enter the job name" ResourceKey="Name">Name: </Label> <Label Class="col-sm-3" For="name" HelpText="Enter the job name" ResourceKey="Name">Name: </Label>
</td> <div class="col-sm-9">
<td> <input id="name" class="form-control" @bind="@_name" maxlength="200" required />
<input id="name" class="form-control" @bind="@_name" /> </div>
</td> </div>
</tr> <div class="row mb-1 align-items-center">
<tr> <Label Class="col-sm-3" For="type" HelpText="The fully qualified job type name" ResourceKey="Type">Type: </Label>
<td> <div class="col-sm-9">
<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 /> <input id="type" class="form-control" @bind="@_jobType" readonly />
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="enabled" HelpText="Select whether you want the job enabled or not" ResourceKey="Enabled">Enabled? </Label>
<Label For="enabled" HelpText="Select whether you want the job enabled or not" ResourceKey="Enabled">Enabled? </Label> <div class="col-sm-9">
</td> <select id="enabled" class="form-select" @bind="@_isEnabled" required>
<td> <option value="True">@SharedLocalizer["Yes"]</option>
<select id="enabled" class="form-control" @bind="@_isEnabled"> <option value="False">@SharedLocalizer["No"]</option>
<option value="True">@Localizer["Yes"]</option>
<option value="False">@Localizer["No"]</option>
</select> </select>
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="runs-every" HelpText="Select how often you want the job to run" ResourceKey="RunsEvery">Runs Every: </Label>
<Label For="runs-every" HelpText="Select how often you want the job to run" ResourceKey="RunsEvery">Runs Every: </Label> <div class="col-sm-9">
</td> <input id="runs-every" class="form-control" @bind="@_interval" maxlength="4" required />
<td> <select id="runs-every" class="form-select" @bind="@_frequency" required>
<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="m">@Localizer["Minute(s)"]</option>
<option value="H">@Localizer["Hour(s)"]</option> <option value="H">@Localizer["Hour(s)"]</option>
<option value="d">@Localizer["Day(s)"]</option> <option value="d">@Localizer["Day(s)"]</option>
<option value="M">@Localizer["Month(s)"]</option> <option value="M">@Localizer["Month(s)"]</option>
</select> </select>
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="retention" HelpText="Number of log entries to retain for this job" ResourceKey="RetentionLog">Retention Log (Items): </Label>
<Label For="starting" HelpText="What time do you want the job to start" ResourceKey="Starting">Starting: </Label> <div class="col-sm-9">
</td> <input id="retention" class="form-control" @bind="@_retentionHistory" maxlength="4" required />
<td> </div>
<input id="starting" class="form-control" @bind="@_startDate" /> </div>
</td> <div class="row mb-1 align-items-center">
</tr> <Label Class="col-sm-3" For="starting" HelpText="Optionally enter the date and time when this job should start executing" ResourceKey="Starting">Starting: </Label>
<tr> <div class="col-sm-9">
<td> <div class="row">
<Label For="ending" HelpText="When do you want the job to end" ResourceKey="Ending">Ending: </Label> <div class="col">
</td> <input id="starting" type="date" class="form-control" @bind="@_startDate" />
<td> </div>
<input id="ending" class="form-control" @bind="@_endDate" /> <div class="col">
</td> <input id="starting" type="text" class="form-control" placeholder="hh:mm" @bind="@_startTime" />
</tr> </div>
<tr> </div>
<td> </div>
<Label For="retention" HelpText="Number of log entries to retain for this job" ResourceKey="RetentionLog">Retention Log (Items): </Label> </div>
</td> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="ending" HelpText="Optionally enter the date and time when this job should stop executing" ResourceKey="Ending">Ending: </Label>
<input id="retention" class="form-control" @bind="@_retentionHistory" /> <div class="col-sm-9">
</td> <div class="row">
</tr> <div class="col">
<tr> <input id="ending" type="date" class="form-control" @bind="@_endDate" />
<td> </div>
<Label For="next" HelpText="Next execution for this job." ResourceKey="NextExecution">Next Execution: </Label> <div class="col">
</td> <input id="ending" type="text" class="form-control" placeholder="hh:mm" @bind="@_endTime" />
<td> </div>
<input id="next" class="form-control" @bind="@_nextExecution" /> </div>
</td> </div>
</tr> </div>
</table> <div class="row mb-1 align-items-center">
<button type="button" class="btn btn-success" @onclick="SaveJob">@Localizer["Save"]</button> <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>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@Localizer["Cancel"]</NavLink> <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 { @code {
private ElementReference form;
private bool validated = false;
private int _jobId; private int _jobId;
private string _name = string.Empty; private string _name = string.Empty;
private string _jobType = string.Empty; private string _jobType = string.Empty;
private string _isEnabled = "True"; private string _isEnabled = "True";
private string _interval = string.Empty; private string _interval = string.Empty;
private string _frequency = string.Empty; private string _frequency = string.Empty;
private string _startDate = string.Empty; private DateTime? _startDate = null;
private string _endDate = string.Empty; private string _startTime = string.Empty;
private DateTime? _endDate = null;
private string _endTime = string.Empty;
private string _retentionHistory = 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; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
@ -109,22 +130,40 @@
_isEnabled = job.IsEnabled.ToString(); _isEnabled = job.IsEnabled.ToString();
_interval = job.Interval.ToString(); _interval = job.Interval.ToString();
_frequency = job.Frequency; _frequency = job.Frequency;
_startDate = (job.StartDate != null) ? job.StartDate.ToString() : string.Empty; _startDate = job.StartDate;
_endDate = (job.EndDate != null) ? job.EndDate.ToString() : string.Empty; 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(); _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) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Loading Job {JobId} {Error}", _jobId, ex.Message); 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() 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); var job = await JobService.GetJobAsync(_jobId);
job.Name = _name; job.Name = _name;
@ -132,35 +171,34 @@
job.IsEnabled = Boolean.Parse(_isEnabled); job.IsEnabled = Boolean.Parse(_isEnabled);
job.Frequency = _frequency; job.Frequency = _frequency;
job.Interval = int.Parse(_interval); job.Interval = int.Parse(_interval);
job.StartDate = _startDate;
if (_startDate == string.Empty) if (job.StartDate != null)
{ {
job.StartDate = null; job.StartDate = job.StartDate.Value.Date;
} if (!string.IsNullOrEmpty(_startTime))
else
{ {
job.StartDate = DateTime.Parse(_startDate); job.StartDate = DateTime.Parse(job.StartDate.Value.ToShortDateString() + " " + _startTime);
} }
}
if (_endDate == string.Empty) job.EndDate = _endDate;
if (job.EndDate != null)
{ {
job.EndDate = null; job.EndDate = job.EndDate.Value.Date;
} if (!string.IsNullOrEmpty(_endTime))
else
{ {
job.EndDate = DateTime.Parse(_endDate); job.EndDate = DateTime.Parse(job.EndDate.Value.ToShortDateString() + " " + _endTime);
} }
if (_nextExecution == string.Empty)
{
job.NextExecution = null;
} }
else
{
job.NextExecution = DateTime.Parse(_nextExecution);
}
job.RetentionHistory = int.Parse(_retentionHistory); 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 try
{ {
@ -171,13 +209,12 @@
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Udate Job {Job} {Error}", job, ex.Message); 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 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 @inherits ModuleBase
@inject IJobService JobService @inject IJobService JobService
@inject IStringLocalizer<Index> Localizer @inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_jobs == null) @if (_jobs == null)
{ {
<p><em>@Localizer["Loading..."]</em></p> <p><em>@SharedLocalizer["Loading"]</em></p>
} }
else else
{ {
@ -19,8 +20,8 @@ else
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th>@Localizer["Name"]</th> <th>@SharedLocalizer["Name"]</th>
<th>@Localizer["Status"]</th> <th>@SharedLocalizer["Status"]</th>
<th>@Localizer["Frequency"]</th> <th>@Localizer["Frequency"]</th>
<th>@Localizer["NextExecution"]</th> <th>@Localizer["NextExecution"]</th>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
@ -118,7 +119,7 @@ else
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Deleting Job {Job} {Error}", job, ex.Message); 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,17 +2,18 @@
@inherits ModuleBase @inherits ModuleBase
@inject IJobLogService JobLogService @inject IJobLogService JobLogService
@inject IStringLocalizer<Log> Localizer @inject IStringLocalizer<Log> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_jobLogs == null) @if (_jobLogs == null)
{ {
<p><em>@Localizer["Loading..."]</em></p> <p><em>@SharedLocalizer["Loading"]</em></p>
} }
else else
{ {
<Pager Items="@_jobLogs"> <Pager Items="@_jobLogs">
<Header> <Header>
<th>@Localizer["Name"]</th> <th>@SharedLocalizer["Name"]</th>
<th>@Localizer["Status"]</th> <th>@SharedLocalizer["Status"]</th>
<th>@Localizer["Started"]</th> <th>@Localizer["Started"]</th>
<th>@Localizer["Finished"]</th> <th>@Localizer["Finished"]</th>
</Header> </Header>

View File

@ -7,10 +7,11 @@
@inject ILanguageService LanguageService @inject ILanguageService LanguageService
@inject IPackageService PackageService @inject IPackageService PackageService
@inject IStringLocalizer<Add> Localizer @inject IStringLocalizer<Add> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_supportedCultures == null) @if (_supportedCultures == null)
{ {
<p><em>@Localizer["Loading..."]</em></p> <p><em>@SharedLocalizer["Loading"]</em></p>
} }
else else
{ {
@ -22,86 +23,157 @@ else
} }
else else
{ {
<table class="table table-borderless"> <form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<tr> <div class="container">
<td> <div class="row mb-1 align-items-center">
<Label For="name" HelpText="Name Of The Language" ResourceKey="Name">Name:</Label> <Label Class="col-sm-3" For="name" HelpText="Name Of The Language" ResourceKey="Name">Name:</Label>
</td> <div class="col-sm-9">
<td> <select id="_code" class="form-select" @bind="@_code" required>
<select id="_code" class="form-control" @bind="@_code">
@foreach (var culture in _availableCultures) @foreach (var culture in _availableCultures)
{ {
<option value="@culture.Name">@culture.DisplayName</option> <option value="@culture.Name">@culture.DisplayName</option>
} }
</select> </select>
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="default" HelpText="Indicates Whether Or Not This Language Is The Default For The Site" ResourceKey="IsDefault">Default?</Label>
<Label For="default" HelpText="Indicates Whether Or Not This Language Is The Default For The Site" ResourceKey="IsDefault">Default?</Label> <div class="col-sm-9">
</td> <select id="default" class="form-select" @bind="@_isDefault" required>
<td> <option value="True">@SharedLocalizer["Yes"]</option>
<select id="default" class="form-control" @bind="@_isDefault"> <option value="False">@SharedLocalizer["No"]</option>
<option value="True">@Localizer["Yes"]</option>
<option value="False">@Localizer["No"]</option>
</select> </select>
</td> </div>
</tr> </div>
</table> </div>
<button type="button" class="btn btn-success" @onclick="SaveLanguage">@Localizer["Save"]</button> <button type="button" class="btn btn-success" @onclick="SaveLanguage">@SharedLocalizer["Save"]</button>
</form>
} }
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@Localizer["Cancel"]</NavLink> <NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
</TabPanel> </TabPanel>
<TabPanel Name="Download" ResourceKey="Download" Security="SecurityAccessLevel.Host"> <TabPanel Name="Download" ResourceKey="Download" Security="SecurityAccessLevel.Host">
@if (_packages != null && _packages.Count > 0) <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)
{ {
<ModuleMessage Type="MessageType.Info" Message="Download one or more language packages from the list below. Once you are ready click Install to complete the installation."></ModuleMessage>
<Pager Items="@_packages"> <Pager Items="@_packages">
<Header>
<th>@Localizer["Name"]</th>
<th>@Localizer["Version"]</th>
<th style="width: 1px"></th>
</Header>
<Row> <Row>
<td>@context.Name</td>
<td>@context.Version</td>
<td> <td>
<button type="button" class="btn btn-primary" @onclick=@(async () => await DownloadLanguage(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>
</Row> <td style="width: 1px; vertical-align: middle;">
</Pager> @if (context.Price != null && !string.IsNullOrEmpty(context.PackageUrl))
<button type="button" class="btn btn-success" @onclick="InstallLanguages">@Localizer["Install"]</button> {
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@Localizer["Cancel"]</NavLink> <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 else
{ {
<ModuleMessage Type="MessageType.Info" Message="No Language Packages Are Available To Download"></ModuleMessage> <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>
<TabPanel Name="Upload" ResourceKey="Upload" Security="SecurityAccessLevel.Host"> <TabPanel Name="Upload" ResourceKey="Upload" Security="SecurityAccessLevel.Host">
<table class="table table-borderless"> <div class="container">
<tr> <div class="row mb-1 align-items-center">
<td> <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>
<Label HelpText="Upload one or more language packages. Once they are uploaded click Install to complete the installation." ResourceKey="Module">Language: </Label> <div class="col-sm-9">
</td> <FileManager Filter="nupkg" ShowFiles="true" Folder="Packages" UploadMultiple="true" />
<td> </div>
<FileManager Filter="nupkg" ShowFiles="false" Folder="Packages" UploadMultiple="true" /> </div>
</td> </div>
</tr> <button type="button" class="btn btn-success" @onclick="InstallLanguages">@SharedLocalizer["Install"]</button>
</table> <NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
<button type="button" class="btn btn-success" @onclick="InstallLanguages">@Localizer["Install"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@Localizer["Cancel"]</NavLink>
</TabPanel> </TabPanel>
</TabStrip> </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 { @code {
private ElementReference form;
private bool validated = false;
private string _code = string.Empty; private string _code = string.Empty;
private string _isDefault = "False"; private string _isDefault = "False";
private string _message; private string _message;
private IEnumerable<Culture> _supportedCultures; private IEnumerable<Culture> _supportedCultures;
private IEnumerable<Culture> _availableCultures; private IEnumerable<Culture> _availableCultures;
private List<Package> _packages; 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; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
@ -113,19 +185,68 @@ else
_supportedCultures = await LocalizationService.GetCulturesAsync(); _supportedCultures = await LocalizationService.GetCulturesAsync();
_availableCultures = _supportedCultures _availableCultures = _supportedCultures
.Where(c => !c.Name.Equals(Constants.DefaultCulture) && !languagesCodes.Contains(c.Name)); .Where(c => !c.Name.Equals(Constants.DefaultCulture) && !languagesCodes.Contains(c.Name));
_packages = await PackageService.GetPackagesAsync("language"); await LoadTranslations();
if (_supportedCultures.Count() == 1) if (_supportedCultures.Count() == 1)
{ {
_message = Localizer["The Only Installed Language Is English"]; _message = Localizer["OnlyEnglish"];
} }
else if (_availableCultures.Count() == 0) else if (_availableCultures.Count() == 0)
{ {
_message = Localizer["All The Installed Languages Have Been Added."]; _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() private async Task SaveLanguage()
{
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{ {
var language = new Language var language = new Language
{ {
@ -151,7 +272,60 @@ else
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Adding Language {Language} {Error}", language, ex.Message); await logger.LogError(ex, "Error Adding Language {Language} {Error}", language, ex.Message);
AddModuleMessage(Localizer["Error Adding Language"], MessageType.Error); AddModuleMessage(Localizer["Error.Language.Add"], MessageType.Error);
}
}
else
{
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
}
}
private void HideModal()
{
_productname = "";
_license = "";
StateHasChanged();
}
private async Task GetPackage(string packageid, string version)
{
try
{
var package = await PackageService.GetPackageAsync(packageid, version);
if (package != null)
{
_productname = package.Name;
if (!string.IsNullOrEmpty(package.License))
{
_license = package.License.Replace("\n", "<br />");
}
_packageid = package.PackageId;
_version = package.Version;
}
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Getting Package {PackageId} {Version}", packageid, version);
AddModuleMessage(Localizer["Error.Module.Download"], MessageType.Error);
}
}
private async Task DownloadPackage()
{
try
{
await PackageService.DownloadPackageAsync(_packageid, _version, "Packages");
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);
} }
} }
@ -160,27 +334,11 @@ else
try try
{ {
await PackageService.InstallPackagesAsync(); await PackageService.InstallPackagesAsync();
AddModuleMessage(Localizer["Language Packages 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.Language.Install"], NavigateUrl("admin/system")), MessageType.Success);
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Installing Language Package"); await logger.LogError(ex, "Error Installing Translations");
}
}
private async Task DownloadLanguage(string packageid, string version)
{
try
{
await PackageService.DownloadPackageAsync(packageid, version, "Packages");
await logger.LogInformation("Language Paclage {Name} {Version} Downloaded Successfully", packageid, version);
AddModuleMessage(Localizer["Language Package Downloaded Successfully. Click Install To Complete Installation."], MessageType.Success);
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Downloading Language Package {Name} {Version}", packageid, version);
AddModuleMessage(Localizer["Error Downloading Language Package"], MessageType.Error);
} }
} }

View File

@ -4,10 +4,11 @@
@inject ILocalizationService LocalizationService @inject ILocalizationService LocalizationService
@inject IPackageService PackageService @inject IPackageService PackageService
@inject IStringLocalizer<Index> Localizer @inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_languages == null) @if (_languages == null)
{ {
<p><em>@Localizer["Loading..."]</em></p> <p><em>@SharedLocalizer["Loading"]</em></p>
} }
else else
{ {
@ -16,20 +17,20 @@ else
<Pager Items="@_languages"> <Pager Items="@_languages">
<Header> <Header>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th>@Localizer["Name"]</th> <th>@SharedLocalizer["Name"]</th>
<th>@Localizer["Code"]</th> <th>@Localizer["Code"]</th>
<th>@Localizer["Default?"]</th> <th>@Localizer["Default"]</th>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
</Header> </Header>
<Row> <Row>
<td><ActionDialog Header="Delete Langauge" Message="@Localizer["Are You Sure You Wish To Delete The {0} Language From This Site?", 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><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.Name</td>
<td>@context.Code</td> <td>@context.Code</td>
<td><TriStateCheckBox Value="@(context.IsDefault)" Disabled="true"></TriStateCheckBox></td> <td><TriStateCheckBox Value="@(context.IsDefault)" Disabled="true"></TriStateCheckBox></td>
<td> <td>
@if (UpgradeAvailable(context.Code)) @if (UpgradeAvailable(context.Code))
{ {
<button type="button" class="btn btn-success" @onclick=@(async () => await DownloadLanguage(context.Code))>@Localizer["Upgrade"]</button> <button type="button" class="btn btn-success" @onclick=@(async () => await DownloadLanguage(context.Code))>@SharedLocalizer["Upgrade"]</button>
} }
</td> </td>
</Row> </Row>
@ -54,7 +55,7 @@ else
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{ {
_packages = await PackageService.GetPackagesAsync("language"); _packages = await PackageService.GetPackagesAsync("translation");
} }
} }
@ -71,7 +72,7 @@ else
{ {
await logger.LogError(ex, "Error Deleting Language {Language} {Error}", language, ex.Message); 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);
} }
} }
@ -97,15 +98,15 @@ else
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{ {
await PackageService.DownloadPackageAsync(Constants.PackageId + ".Client." + code, Constants.Version, "Packages"); await PackageService.DownloadPackageAsync(Constants.PackageId + ".Client." + code, Constants.Version, "Packages");
await logger.LogInformation("Language Package Downloaded {Code} {Version}", code, Constants.Version); await logger.LogInformation("Translation Downloaded {Code} {Version}", code, Constants.Version);
await PackageService.InstallPackagesAsync(); await PackageService.InstallPackagesAsync();
AddModuleMessage(Localizer["Language Package 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.Language.Install"], NavigateUrl("admin/system")), MessageType.Success);
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Downloading Language Package {Code} {Version} {Error}", code, Constants.Version, ex.Message); await logger.LogError(ex, "Error Downloading Translation {Code} {Version} {Error}", code, Constants.Version, ex.Message);
AddModuleMessage(Localizer["Error Downloading Language Package"], MessageType.Error); AddModuleMessage(Localizer["Error.Language.Download"], MessageType.Error);
} }
} }
} }

View File

@ -3,7 +3,9 @@
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject IUserService UserService @inject IUserService UserService
@inject IServiceProvider ServiceProvider @inject IServiceProvider ServiceProvider
@inject SiteState SiteState
@inject IStringLocalizer<Index> Localizer @inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_message != string.Empty) @if (_message != string.Empty)
{ {
@ -14,27 +16,27 @@
<text>...</text> <text>...</text>
</Authorizing> </Authorizing>
<Authorized> <Authorized>
<ModuleMessage Message="@Localizer["You Are Already Signed In"]" Type="MessageType.Info" /> <ModuleMessage Message="@Localizer["Info.SignedIn"]" Type="MessageType.Info" />
</Authorized> </Authorized>
<NotAuthorized> <NotAuthorized>
<form @ref="login" class="@(validated ? "was-validated" : "needs-validation")" novalidate> <form @ref="login" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<div class="container Oqtane-Modules-Admin-Login" @onkeypress="@(e => KeyPressed(e))"> <div class="container Oqtane-Modules-Admin-Login" @onkeypress="@(e => KeyPressed(e))">
<div class="form-group"> <div class="form-group">
<label for="Username" class="control-label">@Localizer["Username:"] </label> <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 /> <input type="text" @ref="username" name="Username" class="form-control username" placeholder="Username" @bind="@_username" id="Username" required />
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="Password" class="control-label">@Localizer["Password:"] </label> <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 /> <input type="password" name="Password" class="form-control password" placeholder="Password" @bind="@_password" id="Password" required />
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="form-check form-check-inline"> <div class="form-check form-check-inline">
<label class="form-check-label" for="Remember">@Localizer["Remember Me?"]</label>&nbsp; <label class="form-check-label" for="Remember">@Localizer["RememberMe"]</label>&nbsp;
<input type="checkbox" class="form-check-input" name="Remember" @bind="@_remember" id="Remember" /> <input type="checkbox" class="form-check-input" name="Remember" @bind="@_remember" id="Remember" />
</div> </div>
</div> </div>
<button type="button" class="btn btn-primary" @onclick="Login">@Localizer["Login"]</button> <button type="button" class="btn btn-primary" @onclick="Login">@SharedLocalizer["Login"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@Localizer["Cancel"]</button> <button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
<br /><br /> <br /><br />
<button type="button" class="btn btn-secondary" @onclick="Forgot">@Localizer["ForgotPassword"]</button> <button type="button" class="btn btn-secondary" @onclick="Forgot">@Localizer["ForgotPassword"]</button>
</div> </div>
@ -82,11 +84,13 @@
if (user != null) 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 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; _type = MessageType.Warning;
} }
} }
@ -95,10 +99,13 @@
protected override async Task OnAfterRenderAsync(bool firstRender) protected override async Task OnAfterRenderAsync(bool firstRender)
{ {
if (firstRender) if (firstRender)
{
if(PageState.User == null)
{ {
await username.FocusAsync(); await username.FocusAsync();
} }
} }
}
private async Task Login() private async Task Login()
{ {
@ -108,7 +115,6 @@
{ {
if (PageState.Runtime == Oqtane.Shared.Runtime.Server) if (PageState.Runtime == Oqtane.Shared.Runtime.Server)
{ {
// server-side Blazor
var user = new User(); var user = new User();
user.SiteId = PageState.Site.SiteId; user.SiteId = PageState.Site.SiteId;
user.Username = _username; user.Username = _username;
@ -117,17 +123,16 @@
if (user.IsAuthenticated) if (user.IsAuthenticated)
{ {
await logger.LogInformation("Login Successful For Username {Username}", _username); await logger.LogInformation(LogFunction.Security, "Login Successful For Username {Username}", _username);
// complete the login on the server so that the cookies are set correctly // server-side Blazor needs to post to the Login page so that the cookies are set correctly
string antiforgerytoken = await interop.GetElementByName("__RequestVerificationToken"); var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, username = _username, password = _password, remember = _remember, returnurl = _returnUrl };
var fields = new { __RequestVerificationToken = antiforgerytoken, username = _username, password = _password, remember = _remember, returnurl = _returnUrl };
string url = Utilities.TenantUrl(PageState.Alias, "/pages/login/"); string url = Utilities.TenantUrl(PageState.Alias, "/pages/login/");
await interop.SubmitForm(url, fields); await interop.SubmitForm(url, fields);
} }
else else
{ {
await logger.LogInformation("Login Failed For Username {Username}", _username); await logger.LogError(LogFunction.Security, "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["Error.Login.Fail"], MessageType.Error);
} }
} }
else else
@ -140,21 +145,21 @@
user = await UserService.LoginUserAsync(user, true, _remember); user = await UserService.LoginUserAsync(user, true, _remember);
if (user.IsAuthenticated) if (user.IsAuthenticated)
{ {
await logger.LogInformation("Login Successful For Username {Username}", _username); await logger.LogInformation(LogFunction.Security, "Login Successful For Username {Username}", _username);
var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider.GetService(typeof(IdentityAuthenticationStateProvider)); var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider.GetService(typeof(IdentityAuthenticationStateProvider));
authstateprovider.NotifyAuthenticationChanged(); authstateprovider.NotifyAuthenticationChanged();
NavigationManager.NavigateTo(NavigateUrl(_returnUrl, "reload")); NavigationManager.NavigateTo(NavigateUrl(_returnUrl, true));
} }
else else
{ {
await logger.LogInformation("Login Failed For Username {Username}", _username); await logger.LogError(LogFunction.Security, "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["Error.Login.Fail"], MessageType.Error);
} }
} }
} }
else else
{ {
AddModuleMessage(Localizer["Please Provide Your Username And Password"], MessageType.Warning); AddModuleMessage(Localizer["Message.Required.UserInfo"], MessageType.Warning);
} }
} }
@ -171,6 +176,7 @@
if (user != null) if (user != null)
{ {
await UserService.ForgotPasswordAsync(user); 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"; _message = "Please Check The Email Address Associated To Your User Account For A Password Reset Notification";
} }
else else

View File

@ -6,135 +6,176 @@
@inject IPageService PageService @inject IPageService PageService
@inject IPageModuleService PageModuleService @inject IPageModuleService PageModuleService
@inject IUserService UserService @inject IUserService UserService
@inject IStringLocalizer<Index> Localizer @inject IStringLocalizer<Detail> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<table class="table table-borderless"> <div class="container">
<tr> <div class="row mb-1 align-items-center">
<td>
<Label For="dateTime" HelpText="The date and time of this log" ResourceKey="DateTime">Date/Time: </Label> <div class="col-sm-9">
</td>
<td> </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 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 /> <input id="dateTime" class="form-control" @bind="@_logDate" readonly />
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="level" HelpText="The level of this log" ResourceKey="Level">Level: </Label>
<Label For="level" HelpText="The level of this log" ResourceKey="Level">Level: </Label> <div class="col-sm-9">
</td>
<td>
<input id="level" class="form-control" @bind="@_level" readonly /> <input id="level" class="form-control" @bind="@_level" readonly />
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="feature" HelpText="The feature that was affected" ResourceKey="Feature">Feature: </Label>
<Label For="feature" HelpText="The feature that was affected" ResourceKey="Feature">Feature: </Label> <div class="col-sm-9">
</td>
<td>
<input id="feature" class="form-control" @bind="@_feature" readonly /> <input id="feature" class="form-control" @bind="@_feature" readonly />
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="function" HelpText="The function that was performed" ResourceKey="Function">Function: </Label>
<Label For="function" HelpText="The function that was performed" ResourceKey="Function">Function: </Label> <div class="col-sm-9">
</td>
<td>
<input id="function" class="form-control" @bind="@_function" readonly /> <input id="function" class="form-control" @bind="@_function" readonly />
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="category" HelpText="The categories that were affected" ResourceKey="Category">Category: </Label>
<Label For="category" HelpText="The categories that were affected" ResourceKey="Category">Category: </Label> <div class="col-sm-9">
</td>
<td>
<input id="category" class="form-control" @bind="@_category" readonly /> <input id="category" class="form-control" @bind="@_category" readonly />
</td> </div>
</tr> </div>
@if (_pageName != string.Empty) @if (_pageName != string.Empty)
{ {
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="page" HelpText="The page that was affected" ResourceKey="Page">Page: </Label>
<Label For="page" HelpText="The page that was affected" ResourceKey="Page">Page: </Label> <div class="col-sm-9">
</td>
<td>
<input id="page" class="form-control" @bind="@_pageName" readonly /> <input id="page" class="form-control" @bind="@_pageName" readonly />
</td> </div>
</tr> </div>
} }
@if (_moduleTitle != string.Empty) @if (_moduleTitle != string.Empty)
{ {
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="module" HelpText="The module that was affected" ResourceKey="Module">Module: </Label>
<Label For="module" HelpText="The module that was affected" ResourceKey="Module">Module: </Label> <div class="col-sm-9">
</td>
<td>
<input id="module" class="form-control" @bind="@_moduleTitle" readonly /> <input id="module" class="form-control" @bind="@_moduleTitle" readonly />
</td> </div>
</tr> </div>
} }
@if (_username != string.Empty) @if (_username != string.Empty)
{ {
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="user" HelpText="The user that caused this log" ResourceKey="User">User: </Label>
<Label For="user" HelpText="The user that caused this log" ResourceKey="User">User: </Label> <div class="col-sm-9">
</td>
<td>
<input id="user" class="form-control" @bind="@_username" readonly /> <input id="user" class="form-control" @bind="@_username" readonly />
</td> </div>
</tr> </div>
} }
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="url" HelpText="The url the log comes from" ResourceKey="Url">Url: </Label>
<Label For="url" HelpText="The url the log comes from" ResourceKey="Url">Url: </Label> <div class="col-sm-9">
</td>
<td>
<input id="url" class="form-control" @bind="@_url" readonly /> <input id="url" class="form-control" @bind="@_url" readonly />
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="template" HelpText="What the log is about" ResourceKey="Template">Template: </Label>
<Label For="template" HelpText="What the log is about" ResourceKey="Template">Template: </Label> <div class="col-sm-9">
</td>
<td>
<input id="template" class="form-control" @bind="@_template" readonly /> <input id="template" class="form-control" @bind="@_template" readonly />
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="message" HelpText="The message that the system generated" ResourceKey="Message">Message: </Label>
<Label For="message" HelpText="The message that the system generated" class="control-label" ResourceKey="Message">Message: </Label> <div class="col-sm-9">
</td>
<td>
<textarea id="message" class="form-control" @bind="@_message" rows="5" readonly></textarea> <textarea id="message" class="form-control" @bind="@_message" rows="5" readonly></textarea>
</td> </div>
</tr> </div>
@if (!string.IsNullOrEmpty(_exception)) @if (!string.IsNullOrEmpty(_exception))
{ {
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="exception" HelpText="The exceptions generated by the system" ResourceKey="Exception">Exception: </Label>
<Label For="exception" HelpText="The exceptions generated by the system" ResourceKey="Exception">Exception: </Label> <div class="col-sm-9">
</td>
<td>
<textarea id="exception" class="form-control" @bind="@_exception" rows="5" readonly></textarea> <textarea id="exception" class="form-control" @bind="@_exception" rows="5" readonly></textarea>
</td> </div>
</tr> </div>
} }
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="properties" HelpText="The properties that were affected" ResourceKey="Properties">Properties: </Label>
<Label For="properties" HelpText="The properties that were affected" ResourceKey="Properties">Properties: </Label> <div class="col-sm-9">
</td>
<td>
<textarea id="properties" class="form-control" @bind="@_properties" rows="5" readonly></textarea> <textarea id="properties" class="form-control" @bind="@_properties" rows="5" readonly></textarea>
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="server" HelpText="The server that was affected" ResourceKey="Server">Server: </Label>
<Label For="server" HelpText="The server that was affected" ResourceKey="Server">Server: </Label> <div class="col-sm-9">
</td>
<td>
<input id="server" class="form-control" @bind="@_server" readonly /> <input id="server" class="form-control" @bind="@_server" readonly />
</td> </div>
</tr> </div>
</table>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@Localizer["Cancel"]</NavLink> <NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
@code { @code {
private int _logId; private int _logId;
@ -207,7 +248,7 @@
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Loading Log {LogId} {Error}", _logId, ex.Message); await logger.LogError(ex, "Error Loading Log {LogId} {Error}", _logId, ex.Message);
AddModuleMessage(Localizer["Error Loading Log"], MessageType.Error); AddModuleMessage(Localizer["Error.Log.Load"], MessageType.Error);
} }
} }
} }

View File

@ -2,18 +2,19 @@
@inherits ModuleBase @inherits ModuleBase
@inject ILogService LogService @inject ILogService LogService
@inject IStringLocalizer<Index> Localizer @inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_logs == null) @if (_logs == null)
{ {
<p><em>@Localizer["Loading..."]</em></p> <p><em>@SharedLocalizer["Loading"]</em></p>
} }
else else
{ {
<table class="table table-borderless"> <div class="container g-0">
<tr> <div class="row mb-1 align-items-center">
<td> <div class="col-sm-4">
<Label For="level" HelpText="Select the log level for event log items" ResourceKey="Level">Level: </Label><br /><br /> <Label For="level" HelpText="Select the log level for event log items" ResourceKey="Level">Level: </Label><br /><br />
<select id="level" class="form-control" @onchange="(e => LevelChanged(e))"> <select id="level" class="form-select" @onchange="(e => LevelChanged(e))">
<option value="-">&lt;@Localizer["AllLevels"]&gt;</option> <option value="-">&lt;@Localizer["AllLevels"]&gt;</option>
<option value="Trace">@Localizer["Trace"]</option> <option value="Trace">@Localizer["Trace"]</option>
<option value="Debug">@Localizer["Debug"]</option> <option value="Debug">@Localizer["Debug"]</option>
@ -22,29 +23,29 @@ else
<option value="Error">@Localizer["Error"]</option> <option value="Error">@Localizer["Error"]</option>
<option value="Critical">@Localizer["Critical"]</option> <option value="Critical">@Localizer["Critical"]</option>
</select> </select>
</td> </div>
<td> <div class="col-sm-4">
<Label For="function" HelpText="Select the function for event log items" ResourceKey="Function">Function: </Label><br /><br /> <Label For="function" HelpText="Select the function for event log items" ResourceKey="Function">Function: </Label><br /><br />
<select id="function" class="form-control" @onchange="(e => FunctionChanged(e))"> <select id="function" class="form-select" @onchange="(e => FunctionChanged(e))">
<option value="-">&lt;@Localizer["AllFunctions"]&gt;</option> <option value="-">&lt;@Localizer["AllFunctions"]&gt;</option>
<option value="Create">@Localizer["Create"]</option> <option value="Create">@Localizer["Create"]</option>
<option value="Read">@Localizer["Read"]</option> <option value="Read">@Localizer["Read"]</option>
<option value="Update">@Localizer["Update"]</option> <option value="Update">@SharedLocalizer["Update"]</option>
<option value="Delete">@Localizer["Delete"]</option> <option value="Delete">@SharedLocalizer["Delete"]</option>
<option value="Security">@Localizer["Security"]</option> <option value="Security">@Localizer["Security"]</option>
<option value="Other">@Localizer["Other"]</option> <option value="Other">@Localizer["Other"]</option>
</select> </select>
</td> </div>
<td> <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 /> <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-control" @onchange="(e => RowsChanged(e))"> <select id="rows" class="form-select" @onchange="(e => RowsChanged(e))">
<option value="10">10</option> <option value="10">10</option>
<option value="50">50</option> <option value="50">50</option>
<option value="100">100</option> <option value="100">100</option>
</select> </select>
</td> </div>
</tr> </div>
</table> </div>
@if (_logs.Any()) @if (_logs.Any())
{ {
@ -67,7 +68,7 @@ else
} }
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) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Loading Logs {Error}", ex.Message); 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) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Loading Logs {Error}", ex.Message); 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) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Loading Logs {Error}", ex.Message); 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) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Loading Logs {Error}", ex.Message); 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

@ -6,54 +6,46 @@
@inject IModuleService ModuleService @inject IModuleService ModuleService
@inject ISettingService SettingService @inject ISettingService SettingService
@inject IStringLocalizer<Index> Localizer @inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (string.IsNullOrEmpty(_moduledefinitionname) && _templates != null) @if (string.IsNullOrEmpty(_moduledefinitionname) && _templates != null)
{ {
<table class="table table-borderless"> <form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<tr> <div class="container">
<td> <div class="row mb-1 align-items-center">
<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> <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>
</td> <div class="col-sm-9">
<td> <input id="owner" class="form-control" @bind="@_owner" required />
<input id="owner" class="form-control" @bind="@_owner" /> </div>
</td> </div>
</tr> <div class="row mb-1 align-items-center">
<tr> <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>
<td> <div class="col-sm-9">
<Label For="module" HelpText="Enter a name for this module. It should not contain spaces or punctuation." ResourceKey="ModuleName">Module Name: </Label> <input id="module" class="form-control" @bind="@_module" required />
</td> </div>
<td> </div>
<input id="module" class="form-control" @bind="@_module" /> <div class="row mb-1 align-items-center">
</td> <Label Class="col-sm-3" For="description" HelpText="Enter a short description for the module" ResourceKey="Description">Description: </Label>
</tr> <div class="col-sm-9">
<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> <textarea id="description" class="form-control" @bind="@_description" rows="3" ></textarea>
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <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>
<Label 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">
</td> <select id="template" class="form-select" @onchange="(e => TemplateChanged(e))" required>
<td> <option value="-">&lt;@Localizer["Template.Select"]&gt;</option>
<select id="template" class="form-control" @onchange="(e => TemplateChanged(e))">
<option value="-">&lt;@Localizer["Select Template"]&gt;</option>
@foreach (Template template in _templates) @foreach (Template template in _templates)
{ {
<option value="@template.Name">@template.Title</option> <option value="@template.Name">@template.Title</option>
} }
</select> </select>
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="reference" HelpText="Select a framework reference version" ResourceKey="FrameworkReference">Framework Reference: </Label>
<Label For="reference" HelpText="Select a framework reference version" ResourceKey="FrameworkReference">Framework Reference: </Label> <div class="col-sm-9">
</td> <select id="reference" class="form-select" @bind="@_reference" required>
<td>
<select id="reference" class="form-control" @bind="@_reference">
@foreach (string version in _versions) @foreach (string version in _versions)
{ {
if (Version.Parse(version).CompareTo(Version.Parse(_minversion)) >= 0) if (Version.Parse(version).CompareTo(Version.Parse(_minversion)) >= 0)
@ -61,30 +53,32 @@
<option value="@(version)">@(version)</option> <option value="@(version)">@(version)</option>
} }
} }
<option value="local">@Localizer["Local Version"]</option> <option value="local">@SharedLocalizer["LocalVersion"]</option>
</select> </select>
</td> </div>
</tr> </div>
@if (!string.IsNullOrEmpty(_location)) @if (!string.IsNullOrEmpty(_location))
{ {
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="location" HelpText="Location where the module will be created" ResourceKey="Location">Location: </Label>
<Label For="location" HelpText="Location where the module will be created" ResourceKey="Location">Location: </Label> <div class="col-sm-9">
</td>
<td>
<input id="module" class="form-control" @bind="@_location" readonly /> <input id="module" class="form-control" @bind="@_location" readonly />
</td> </div>
</tr> </div>
} }
</table> </div>
<button type="button" class="btn btn-success" @onclick="CreateModule">@Localizer["Create Module"]</button>
<button type="button" class="btn btn-success" @onclick="CreateModule">@Localizer["Module.Create"]</button>
} }
else 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>
</form>
} }
@code { @code {
private ElementReference form;
private bool validated = false;
private string _moduledefinitionname = string.Empty; private string _moduledefinitionname = string.Empty;
private string _owner = string.Empty; private string _owner = string.Empty;
private string _module = string.Empty; private string _module = string.Empty;
@ -108,11 +102,11 @@ else
if (string.IsNullOrEmpty(_moduledefinitionname)) if (string.IsNullOrEmpty(_moduledefinitionname))
{ {
AddModuleMessage(Localizer["Please Note That The Module Creator Is Only Intended To Be Used In A Development Environment"], MessageType.Info); AddModuleMessage(Localizer["Info.Module.Creator"], MessageType.Info);
} }
else 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);
} }
} }
catch (Exception ex) catch (Exception ex)
@ -123,9 +117,11 @@ else
private async Task CreateModule() 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 }; var moduleDefinition = new ModuleDefinition { Owner = _owner, Name = _module, Description = _description, Template = _template, Version = _reference };
moduleDefinition = await ModuleDefinitionService.CreateModuleDefinitionAsync(moduleDefinition); moduleDefinition = await ModuleDefinitionService.CreateModuleDefinitionAsync(moduleDefinition);
@ -136,18 +132,18 @@ else
GetLocation(); GetLocation();
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); AddModuleMessage(string.Format(Localizer["Success.Module.Create"], NavigateUrl("admin/system")), MessageType.Success);
}
else
{
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);
}
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Creating Module"); await logger.LogError(ex, "Error Creating Module");
} }
} }
else
{
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
}
}
private async Task ActivateModule() private async Task ActivateModule()
{ {

View File

@ -5,50 +5,122 @@
@inject IModuleDefinitionService ModuleDefinitionService @inject IModuleDefinitionService ModuleDefinitionService
@inject IPackageService PackageService @inject IPackageService PackageService
@inject IStringLocalizer<Add> Localizer @inject IStringLocalizer<Add> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<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) @if (_packages != null)
{ {
<TabStrip> if (_packages.Count > 0)
@if (_packages.Count > 0)
{ {
<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>
<Pager Items="@_packages"> <Pager Items="@_packages">
<Header>
<th>@Localizer["Name"]</th>
<th>@Localizer["Version"]</th>
<th style="width: 1px"></th>
</Header>
<Row> <Row>
<td>@context.Name</td>
<td>@context.Version</td>
<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> </td>
</Row> </Row>
</Pager> </Pager>
</TabPanel>
} }
else
{
<br />
<div class="mx-auto text-center">
@Localizer["Search.NoResults"]
</div>
}
}
</TabPanel>
<TabPanel Name="Upload" ResourceKey="Upload"> <TabPanel Name="Upload" ResourceKey="Upload">
<table class="table table-borderless"> <div class="container">
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" HelpText="Upload one or more module packages. Once they are uploaded click Install to complete the installation." ResourceKey="Module">Module: </Label>
<Label 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">
</td>
<td>
<FileManager Filter="nupkg" ShowFiles="false" Folder="Packages" UploadMultiple="true" /> <FileManager Filter="nupkg" ShowFiles="false" Folder="Packages" UploadMultiple="true" />
</td> </div>
</tr> </div>
</table> </div>
</TabPanel> </TabPanel>
</TabStrip> </TabStrip>
<button type="button" class="btn btn-success" @onclick="InstallModules">@Localizer["Install"]</button> @if (_productname != "")
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@Localizer["Cancel"]</NavLink> {
<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 { @code {
private List<Package> _packages; 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; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
@ -56,9 +128,22 @@
{ {
try try
{ {
var moduledefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId); await LoadModuleDefinitions();
_packages = await PackageService.GetPackagesAsync("module"); }
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()) foreach (Package package in _packages.ToArray())
{ {
if (moduledefinitions.Exists(item => item.PackageName == package.PackageId)) if (moduledefinitions.Exists(item => item.PackageName == package.PackageId))
@ -67,10 +152,94 @@
} }
} }
} }
}
private async void PriceChanged(ChangeEventArgs e)
{
try
{
_price = (string)e.Value;
_search = "";
await LoadModuleDefinitions();
StateHasChanged();
}
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Loading Packages {Error}", ex.Message); await logger.LogError(ex, "Error On PriceChanged");
AddModuleMessage(Localizer["Error Loading Packages"], MessageType.Error); }
}
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, "Packages");
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 try
{ {
await ModuleDefinitionService.InstallModuleDefinitionsAsync(); 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) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Installing Module"); await logger.LogError(ex, "Error Installing Module");
} }
} }
private async Task DownloadModule(string packageid, string version)
{
try
{
await PackageService.DownloadPackageAsync(packageid, version, "Packages");
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

@ -5,55 +5,47 @@
@inject IModuleDefinitionService ModuleDefinitionService @inject IModuleDefinitionService ModuleDefinitionService
@inject IModuleService ModuleService @inject IModuleService ModuleService
@inject ISettingService SettingService @inject ISettingService SettingService
@inject IStringLocalizer<Index> Localizer @inject IStringLocalizer<Create> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_templates != null) @if (_templates != null)
{ {
<table class="table table-borderless"> <form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<tr> <div class="container">
<td> <div class="row mb-1 align-items-center">
<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> <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>
</td> <div class="col-sm-9">
<td> <input id="owner" class="form-control" @bind="@_owner" required />
<input id="owner" class="form-control" @bind="@_owner" /> </div>
</td> </div>
</tr> <div class="row mb-1 align-items-center">
<tr> <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>
<td> <div class="col-sm-9">
<Label For="module" HelpText="Enter a name for this module. It should not contain spaces or punctuation." ResourceKey="ModuleName">Module Name: </Label> <input id="module" class="form-control" @bind="@_module" required />
</td> </div>
<td> </div>
<input id="module" class="form-control" @bind="@_module" /> <div class="row mb-1 align-items-center">
</td> <Label Class="col-sm-3" For="description" HelpText="Enter a short description for the module" ResourceKey="Description">Description: </Label>
</tr> <div class="col-sm-9">
<tr> <textarea id="description" class="form-control" @bind="@_description" rows="3" maxlength="2000" required></textarea>
<td> </div>
<Label For="description" HelpText="Enter a short description for the module" ResourceKey="Description">Description: </Label> </div>
</td> <div class="row mb-1 align-items-center">
<td> <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>
<textarea id="description" class="form-control" @bind="@_description" rows="3"></textarea> <div class="col-sm-9">
</td> <select id="template" class="form-select" @onchange="(e => TemplateChanged(e))" required>
</tr> <option value="-">&lt;@Localizer["Template.Select"]&gt;</option>
<tr>
<td>
<Label For="template" HelpText="Select a module template. Templates are located in the wwwroot/Modules/Templates folder on the server." ResourceKey="Template">Template: </Label>
</td>
<td>
<select id="template" class="form-control" @onchange="(e => TemplateChanged(e))">
<option value="-">&lt;@Localizer["Select Template"]&gt;</option>
@foreach (Template template in _templates) @foreach (Template template in _templates)
{ {
<option value="@template.Name">@template.Title</option> <option value="@template.Name">@template.Title</option>
} }
</select> </select>
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="reference" HelpText="Select a framework reference version" ResourceKey="FrameworkReference">Framework Reference: </Label>
<Label For="reference" HelpText="Select a framework reference version" ResourceKey="FrameworkReference">Framework Reference: </Label> <div class="col-sm-9">
</td> <select id="reference" class="form-select" @bind="@_reference" required>
<td>
<select id="reference" class="form-control" @bind="@_reference">
@foreach (string version in _versions) @foreach (string version in _versions)
{ {
if (Version.Parse(version).CompareTo(Version.Parse(_minversion)) >= 0) if (Version.Parse(version).CompareTo(Version.Parse(_minversion)) >= 0)
@ -61,27 +53,28 @@
<option value="@(version)">@(version)</option> <option value="@(version)">@(version)</option>
} }
} }
<option value="local">@Localizer["Local Version"]</option> <option value="local">@SharedLocalizer["LocalVersion"]</option>
</select> </select>
</td> </div>
</tr> </div>
@if (!string.IsNullOrEmpty(_location)) @if (!string.IsNullOrEmpty(_location))
{ {
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="location" HelpText="Location where the module will be created" ResourceKey="Location">Location: </Label>
<Label For="location" HelpText="Location where the module will be created" ResourceKey="Location">Location: </Label> <div class="col-sm-9">
</td>
<td>
<input id="module" class="form-control" @bind="@_location" readonly /> <input id="module" class="form-control" @bind="@_location" readonly />
</td> </div>
</tr> </div>
} }
</table> </div>
<button type="button" class="btn btn-success" @onclick="CreateModule">@Localizer["CreateModule"]</button> <button type="button" class="btn btn-success" @onclick="CreateModule">@Localizer["CreateModule"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@Localizer["Cancel"]</NavLink> <NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
</form>
} }
@code { @code {
private ElementReference form;
private bool validated = false;
private string _owner = string.Empty; private string _owner = string.Empty;
private string _module = string.Empty; private string _module = string.Empty;
private string _description = string.Empty; private string _description = string.Empty;
@ -100,7 +93,7 @@
{ {
_templates = await ModuleDefinitionService.GetModuleDefinitionTemplatesAsync(); _templates = await ModuleDefinitionService.GetModuleDefinitionTemplatesAsync();
_versions = Constants.ReleaseVersions.Split(',').Where(item => Version.Parse(item).CompareTo(Version.Parse("2.0.0")) >= 0).ToArray(); _versions = Constants.ReleaseVersions.Split(',').Where(item => Version.Parse(item).CompareTo(Version.Parse("2.0.0")) >= 0).ToArray();
AddModuleMessage(Localizer["Please Note That The Module Creator Is Only Intended To Be Used In A Development Environment"], MessageType.Info); AddModuleMessage(Localizer["Info.Module.Development"], MessageType.Info);
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -109,6 +102,10 @@
} }
private async Task CreateModule() private async Task CreateModule()
{
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{ {
try try
{ {
@ -117,11 +114,11 @@
var moduleDefinition = new ModuleDefinition { Owner = _owner, Name = _module, Description = _description, Template = _template, Version = _reference }; var moduleDefinition = new ModuleDefinition { Owner = _owner, Name = _module, Description = _description, Template = _template, Version = _reference };
moduleDefinition = await ModuleDefinitionService.CreateModuleDefinitionAsync(moduleDefinition); moduleDefinition = await ModuleDefinitionService.CreateModuleDefinitionAsync(moduleDefinition);
GetLocation(); GetLocation();
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 Activate The Module.", NavigateUrl("admin/system")], MessageType.Success); AddModuleMessage(string.Format(Localizer["Success.Module.Create"], NavigateUrl("admin/system")), MessageType.Success);
} }
else else
{ {
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); AddModuleMessage(Localizer["Message.Require.ValidName"], MessageType.Warning);
} }
} }
catch (Exception ex) catch (Exception ex)
@ -129,6 +126,11 @@
await logger.LogError(ex, "Error Creating Module"); await logger.LogError(ex, "Error Creating Module");
} }
} }
else
{
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
}
}
private bool IsValid(string name) private bool IsValid(string name)
{ {

View File

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

View File

@ -4,23 +4,25 @@
@inject IModuleDefinitionService ModuleDefinitionService @inject IModuleDefinitionService ModuleDefinitionService
@inject IPackageService PackageService @inject IPackageService PackageService
@inject IStringLocalizer<Index> Localizer @inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_moduleDefinitions == null) @if (_moduleDefinitions == null)
{ {
<p><em>@Localizer["Loading..."]</em></p> <p><em>@SharedLocalizer["Loading"]</em></p>
} }
else else
{ {
<ActionLink Action="Add" Text="Install Module" ResourceKey="InstallModule" /> <ActionLink Action="Add" Text="Install Module" ResourceKey="InstallModule" />
@((MarkupString)"&nbsp;") @((MarkupString)"&nbsp;")
<ActionLink Action="Create" Text="Create Module" ResourceKey="CreateModule" /> <ActionLink Action="Create" Text="Create Module" ResourceKey="CreateModule" Class="btn btn-secondary" />
<Pager Items="@_moduleDefinitions"> <Pager Items="@_moduleDefinitions">
<Header> <Header>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th>@Localizer["Name"]</th> <th>@SharedLocalizer["Name"]</th>
<th>@Localizer["Version"]</th> <th>@SharedLocalizer["Version"]</th>
<th>@SharedLocalizer["Expires"]</th>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
</Header> </Header>
<Row> <Row>
@ -28,15 +30,18 @@ else
<td> <td>
@if (context.AssemblyName != "Oqtane.Client") @if (context.AssemblyName != "Oqtane.Client")
{ {
<ActionDialog Header="Delete Module" Message="@Localizer["Are You Sure You Wish To Delete The {0} Module?", context.Name]" 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>
<td>@context.Name</td> <td>@context.Name</td>
<td>@context.Version</td> <td>@context.Version</td>
<td>
@((MarkupString)PurchaseLink(context.PackageName))
</td>
<td> <td>
@if (UpgradeAvailable(context.PackageName, context.Version)) @if (UpgradeAvailable(context.PackageName, context.Version))
{ {
<button type="button" class="btn btn-success" @onclick=@(async () => await DownloadModule(context.PackageName, context.Version))>@Localizer["Upgrade"]</button> <button type="button" class="btn btn-success" @onclick=@(async () => await DownloadModule(context.PackageName, context.Version))>@SharedLocalizer["Upgrade"]</button>
} }
</td> </td>
</Row> </Row>
@ -61,15 +66,36 @@ else
if (_moduleDefinitions == null) if (_moduleDefinitions == null)
{ {
await logger.LogError(ex, "Error Loading Modules {Error}", ex.Message); 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 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) private bool UpgradeAvailable(string packagename, string version)
{ {
var upgradeavailable = false; var upgradeavailable = false;
if (_packages != null) if (!string.IsNullOrEmpty(packagename) && _packages != null)
{ {
var package = _packages.Where(item => item.PackageId == packagename).FirstOrDefault(); var package = _packages.Where(item => item.PackageId == packagename).FirstOrDefault();
if (package != null) if (package != null)
@ -88,12 +114,12 @@ else
await PackageService.DownloadPackageAsync(packagename, version, "Packages"); await PackageService.DownloadPackageAsync(packagename, version, "Packages");
await logger.LogInformation("Module Downloaded {ModuleDefinitionName} {Version}", packagename, version); await logger.LogInformation("Module Downloaded {ModuleDefinitionName} {Version}", packagename, version);
await ModuleDefinitionService.InstallModuleDefinitionsAsync(); 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) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Downloading Module {ModuleDefinitionName} {Version} {Error}", packagename, version, ex.Message); await logger.LogError(ex, "Error Downloading Module {ModuleDefinitionName} {Version} {Error}", packagename, version, ex.Message);
AddModuleMessage(Localizer["Error Downloading Module"], MessageType.Error); AddModuleMessage(Localizer["Error.Module.Download"], MessageType.Error);
} }
} }
@ -102,13 +128,13 @@ else
try try
{ {
await ModuleDefinitionService.DeleteModuleDefinitionAsync(moduleDefinition.ModuleDefinitionId, moduleDefinition.SiteId); await ModuleDefinitionService.DeleteModuleDefinitionAsync(moduleDefinition.ModuleDefinitionId, moduleDefinition.SiteId);
AddModuleMessage(Localizer["Module Deleted Successfully"], MessageType.Success); AddModuleMessage(Localizer["Success.Module.Delete"], MessageType.Success);
NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, "reload")); NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, true));
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Deleting Module {ModuleDefinition} {Error}", moduleDefinition, ex.Message); 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 NavigationManager NavigationManager
@inject IModuleService ModuleService @inject IModuleService ModuleService
@inject IStringLocalizer<Export> Localizer @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> <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 { @code {
private string _content = string.Empty; private string _content = string.Empty;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
public override string Title => "Export Content"; public override string Title => "Export Content";
private async Task ExportModule() private async Task ExportModule()
{
try
{ {
_content = await ModuleService.ExportModuleAsync(ModuleState.ModuleId); _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,30 +3,36 @@
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject IModuleService ModuleService @inject IModuleService ModuleService
@inject IStringLocalizer<Import> Localizer @inject IStringLocalizer<Import> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<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>
<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> <button type="button" class="btn btn-success" @onclick="ImportModule">@Localizer["Import"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@Localizer["Cancel"]</NavLink> <NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
</form>
@code { @code {
private string _content = string.Empty; private string _content = string.Empty;
private ElementReference form;
private bool validated = false;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
public override string Title => "Import Content"; public override string Title => "Import Content";
private async Task ImportModule() private async Task ImportModule()
{
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{ {
if (_content != string.Empty) if (_content != string.Empty)
{ {
@ -35,22 +41,27 @@
bool success = await ModuleService.ImportModuleAsync(ModuleState.ModuleId, _content); bool success = await ModuleService.ImportModuleAsync(ModuleState.ModuleId, _content);
if (success) if (success)
{ {
AddModuleMessage(Localizer["Content Imported Successfully"], MessageType.Success); AddModuleMessage(Localizer["Success.Content.Import"], MessageType.Success);
} }
else else
{ {
AddModuleMessage(Localizer["A Problem Was Encountered Importing Content. Please Ensure The Content Is Formatted Correctly For The Module."], MessageType.Warning); AddModuleMessage(Localizer["Message.Content.ImportProblem"], MessageType.Warning);
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Importing Module {ModuleId} {Error}", ModuleState.ModuleId, ex.Message); await logger.LogError(ex, "Error Importing Module {ModuleId} {Error}", ModuleState.ModuleId, ex.Message);
AddModuleMessage(Localizer["Error Importing Module"], MessageType.Error); AddModuleMessage(Localizer["Error.Module.Import"], MessageType.Error);
} }
} }
else else
{ {
AddModuleMessage(Localizer["You Must Enter Some Content To Import"], MessageType.Warning); AddModuleMessage(Localizer["Message.Required.ImportContent"], MessageType.Warning);
}
}
else
{
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
} }
} }
} }

View File

@ -6,50 +6,44 @@
@inject IModuleService ModuleService @inject IModuleService ModuleService
@inject IPageModuleService PageModuleService @inject IPageModuleService PageModuleService
@inject IStringLocalizer<Settings> Localizer @inject IStringLocalizer<Settings> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<TabStrip> <TabStrip>
<TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings"> <TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings">
@if (_containers != null) @if (_containers != null)
{ {
<table class="table table-borderless"> <div class="container">
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="title" HelpText="Enter the title of the module" ResourceKey="Title">Title: </Label>
<Label For="title" HelpText="Enter the title of the module" ResourceKey="Title">Title: </Label> <div class="col-sm-9">
</td> <input id="title" type="text" name="Title" class="form-control" @bind="@_title" required />
<td> </div>
<input id="title" type="text" name="Title" class="form-control" @bind="@_title" /> </div>
</td> <div class="row mb-1 align-items-center">
</tr> <Label Class="col-sm-3" For="container" HelpText="Select the module's container" ResourceKey="Container">Container: </Label>
<tr> <div class="col-sm-9">
<td> <select id="container" class="form-select" @bind="@_containerType" required>
<Label For="container" HelpText="Select the module's container" ResourceKey="Container">Container: </Label>
</td>
<td>
<select id="container" class="form-control" @bind="@_containerType">
@foreach (var container in _containers) @foreach (var container in _containers)
{ {
<option value="@container.TypeName">@container.Name</option> <option value="@container.TypeName">@container.Name</option>
} }
</select> </select>
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <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>
<Label 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">
</td> <select id="allpages" class="form-select" @bind="@_allPages" required>
<td> <option value="True">@SharedLocalizer["Yes"]</option>
<select id="allpages" class="form-control" @bind="@_allPages"> <option value="False">@SharedLocalizer["No"]</option>
<option value="True">@Localizer["Yes"]</option>
<option value="False">@Localizer["No"]</option>
</select> </select>
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="page" HelpText="The page that the module is located on" ResourceKey="Page">Page: </Label>
<Label For="page" HelpText="The page that the module is located on" ResourceKey="Page">Page: </Label> <div class="col-sm-9">
</td> <select id="page" class="form-select" @bind="@_pageId" required>
<td>
<select id="page" class="form-control" @bind="@_pageId">
@foreach (Page p in PageState.Pages) @foreach (Page p in PageState.Pages)
{ {
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.Permissions)) if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.Permissions))
@ -58,21 +52,20 @@
} }
} }
</select> </select>
</td> </div>
</tr> </div>
</table> </div>
} }
</TabPanel> </TabPanel>
<TabPanel Name="Permissions" ResourceKey="Permissions"> <TabPanel Name="Permissions" ResourceKey="Permissions">
@if (_permissions != null) @if (_permissions != null)
{ {
<table class="table table-borderless"> <div class="container">
<tr> <div class="row mb-1 align-items-center">
<td>
<PermissionGrid EntityName="@EntityNames.Module" PermissionNames="@_permissionNames" Permissions="@_permissions" @ref="_permissionGrid" /> <PermissionGrid EntityName="@EntityNames.Module" PermissionNames="@_permissionNames" Permissions="@_permissions" @ref="_permissionGrid" />
</td> </div>
</tr> </div>
</table>
} }
</TabPanel> </TabPanel>
@if (_moduleSettingsType != null) @if (_moduleSettingsType != null)
@ -88,13 +81,20 @@
</TabPanel> </TabPanel>
} }
</TabStrip> </TabStrip>
<button type="button" class="btn btn-success" @onclick="SaveModule">@Localizer["Save"]</button> <br />
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@Localizer["Cancel"]</NavLink> <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 { @code {
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
public override string Title => "Module Settings"; public override string Title => "Module Settings";
private ElementReference form;
private bool validated = false;
private List<Theme> _themes; private List<Theme> _themes;
private List<ThemeControl> _containers = new List<ThemeControl>(); private List<ThemeControl> _containers = new List<ThemeControl>();
private string _title; private string _title;
@ -111,6 +111,10 @@
private Type _containerSettingsType; private Type _containerSettingsType;
private object _containerSettings; private object _containerSettings;
private RenderFragment ContainerSettingsComponent { get; set; } private RenderFragment ContainerSettingsComponent { get; set; }
private string createdby;
private DateTime createdon;
private string modifiedby;
private DateTime modifiedon;
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
@ -122,6 +126,10 @@
_permissions = ModuleState.Permissions; _permissions = ModuleState.Permissions;
_permissionNames = ModuleState.ModuleDefinition.PermissionNames; _permissionNames = ModuleState.ModuleDefinition.PermissionNames;
_pageId = ModuleState.PageId.ToString(); _pageId = ModuleState.PageId.ToString();
createdby = ModuleState.CreatedBy;
createdon = ModuleState.CreatedOn;
modifiedby = ModuleState.ModifiedBy;
modifiedon = ModuleState.ModifiedOn;
if (!string.IsNullOrEmpty(ModuleState.ModuleDefinition.SettingsType)) if (!string.IsNullOrEmpty(ModuleState.ModuleDefinition.SettingsType))
{ {
@ -166,6 +174,10 @@
} }
private async Task SaveModule() private async Task SaveModule()
{
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{ {
if (!string.IsNullOrEmpty(_title)) if (!string.IsNullOrEmpty(_title))
{ {
@ -212,7 +224,12 @@
} }
else else
{ {
AddModuleMessage(Localizer["You Must Provide A Title For The Module"], MessageType.Warning); AddModuleMessage(Localizer["Message.Required.Title"], MessageType.Warning);
}
}
else
{
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
} }
} }

View File

@ -4,40 +4,37 @@
@inject IPageService PageService @inject IPageService PageService
@inject IThemeService ThemeService @inject IThemeService ThemeService
@inject IStringLocalizer<Add> Localizer @inject IStringLocalizer<Add> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<TabStrip Refresh="@_refresh"> <TabStrip Refresh="@_refresh">
<TabPanel Name="Settings" ResourceKey="Settings"> <TabPanel Name="Settings" ResourceKey="Settings">
@if (_themeList != null) @if (_themeList != null)
{ {
<table class="table table-borderless"> <div class="container">
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="name" HelpText="Enter the page name" ResourceKey="Name">Name: </Label>
<Label For="Name" HelpText="Enter the page name" ResourceKey="Name">Name: </Label> <div class="col-sm-9">
</td> <input id="name" class="form-control" @bind="@_name" required />
<td> </div>
<input id="Name" class="form-control" @bind="@_name" /> </div>
</td> <div class="row mb-1 align-items-center">
</tr> <Label Class="col-sm-3" For="parent" HelpText="Select the parent for the page in the site hierarchy" ResourceKey="Parent">Parent: </Label>
<tr> <div class="col-sm-9">
<td> <select id="parent" class="form-select" @onchange="(e => ParentChanged(e))" required>
<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["SiteRoot"]&gt;</option> <option value="-1">&lt;@Localizer["SiteRoot"]&gt;</option>
@foreach (Page page in _pageList) @foreach (Page page in _pageList)
{ {
<option value="@(page.PageId)">@(new string('-', page.Level * 2))@(page.Name)</option> <option value="@(page.PageId)">@(new string('-', page.Level * 2))@(page.Name)</option>
} }
</select> </select>
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="insert" HelpText="Select the location where you would like the page to be inserted in relation to other pages" ResourceKey="Insert">Insert: </Label>
<Label 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">
</td> <select id="insert" class="form-select" @bind="@_insert" required>
<td>
<select id="Insert" class="form-control" @bind="@_insert">
<option value="<<">@Localizer["AtBeginning"]</option> <option value="<<">@Localizer["AtBeginning"]</option>
@if (_children != null && _children.Count > 0) @if (_children != null && _children.Count > 0)
{ {
@ -48,112 +45,104 @@
</select> </select>
@if (_children != null && _children.Count > 0 && (_insert == "<" || _insert == ">")) @if (_children != null && _children.Count > 0 && (_insert == "<" || _insert == ">"))
{ {
<select class="form-control" @bind="@_childid"> <select class="form-select" @bind="@_childid">
<option value="-1">&lt;@Localizer["Select Page"]&gt;</option> <option value="-1">&lt;@Localizer["Page.Select"]&gt;</option>
@foreach (Page page in _children) @foreach (Page page in _children)
{ {
<option value="@(page.PageId)">@(page.Name)</option> <option value="@(page.PageId)">@(page.Name)</option>
} }
</select> </select>
} }
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="navigation" HelpText="Select whether the page is part of the site navigation or hidden" ResourceKey="Navigation">Navigation? </Label>
<Label For="Navigation" HelpText="Select whether the page is part of the site navigation or hidden" ResourceKey="Navigation">Navigation? </Label> <div class="col-sm-9">
</td> <select id="navigation" class="form-select" @bind="@_isnavigation" required>
<td> <option value="True">@SharedLocalizer["Yes"]</option>
<select id="Navigation" class="form-control" @bind="@_isnavigation"> <option value="False">@SharedLocalizer["No"]</option>
<option value="True">@Localizer["Yes"]</option>
<option value="False">@Localizer["No"]</option>
</select> </select>
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="clickable" HelpText="Select whether the link in the site navigation is enabled or disabled" ResourceKey="Clickable">Clickable? </Label>
<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> <div class="col-sm-9">
</td> <select id="clickable" class="form-select" @bind="@_isclickable" required>
<td> <option value="True">@SharedLocalizer["Yes"]</option>
<input id="Path" class="form-control" @bind="@_path" /> <option value="False">@SharedLocalizer["No"]</option>
</td> </select>
</tr> </div>
<tr> </div>
<td> <div class="row mb-1 align-items-center">
<Label For="Url" HelpText="Optionally enter a url which this page should redirect to when a user navigates to it" ResourceKey="Redirect">Redirect: </Label> <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." ResourceKey="UrlPath">Url Path: </Label>
</td> <div class="col-sm-9">
<td> <input id="path" class="form-control" @bind="@_path" />
<input id="Url" class="form-control" @bind="@_url" /> </div>
</td> </div>
</tr> <div class="row mb-1 align-items-center">
</table> <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"> <Section Name="Appearance" ResourceKey="Appearance">
<table class="table table-borderless"> <div class="container">
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="title" HelpText="Optionally enter the page title. If you do not provide a page title, the page name will be used." ResourceKey="Title">Title: </Label>
<Label 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">
</td> <input id="title" class="form-control" @bind="@_title" />
<td> </div>
<input id="Title" class="form-control" @bind="@_title" /> </div>
</td> <div class="row mb-1 align-items-center">
</tr> <Label Class="col-sm-3" For="theme" HelpText="Select the theme for this page" ResourceKey="Theme">Theme: </Label>
<tr> <div class="col-sm-9">
<td> <select id="theme" class="form-select" value="@_themetype" @onchange="(e => ThemeChanged(e))" required>
<Label For="Theme" HelpText="Select the theme for this page" ResourceKey="Theme">Theme: </Label>
</td>
<td>
<select id="Theme" class="form-control" value="@_themetype" @onchange="(e => ThemeChanged(e))">
@foreach (var theme in _themes) @foreach (var theme in _themes)
{ {
<option value="@theme.TypeName">@theme.Name</option> <option value="@theme.TypeName">@theme.Name</option>
} }
</select> </select>
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="container" HelpText="Select the default container for the page" ResourceKey="DefaultContainer">Default Container: </Label>
<Label For="defaultContainer" HelpText="Select the default container for the page" ResourceKey="DefaultContainer">Default Container: </Label> <div class="col-sm-9">
</td> <select id="container" class="form-select" @bind="@_containertype" required>
<td> <option value="-">&lt;@Localizer["Container.Select"]&gt;</option>
<select id="defaultContainer" class="form-control" @bind="@_containertype">
<option value="-">&lt;@Localizer["Select Container"]&gt;</option>
@foreach (var container in _containers) @foreach (var container in _containers)
{ {
<option value="@container.TypeName">@container.Name</option> <option value="@container.TypeName">@container.Name</option>
} }
</select> </select>
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="icon" HelpText="Optionally provide an icon class name for this page which will be displayed in the site navigation" ResourceKey="Icon">Icon: </Label>
<Label 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">
</td> <input id="icon" class="form-control" @bind="@_icon" />
<td> </div>
<input id="Icon" class="form-control" @bind="@_icon" /> </div>
</td> <div class="row mb-1 align-items-center">
</tr> <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>
<tr> <div class="col-sm-9">
<td> <select id="personalizable" class="form-select" @bind="@_ispersonalizable" required>
<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> <option value="True">@SharedLocalizer["Yes"]</option>
</td> <option value="False">@SharedLocalizer["No"]</option>
<td>
<select id="Personalizable" class="form-control" @bind="@_ispersonalizable">
<option value="True">@Localizer["Yes"]</option>
<option value="False">@Localizer["No"]</option>
</select> </select>
</td> </div>
</tr> </div>
</table> </div>
</Section> </Section>
} }
</TabPanel> </TabPanel>
<TabPanel Name="Permissions" ResourceKey="Permissions"> <TabPanel Name="Permissions" ResourceKey="Permissions">
<table class="table table-borderless"> <div class="container">
<tr> <div class="row mb-1 align-items-center">
<td>
<PermissionGrid EntityName="@EntityNames.Page" Permissions="@_permissions" @ref="_permissionGrid" /> <PermissionGrid EntityName="@EntityNames.Page" Permissions="@_permissions" @ref="_permissionGrid" />
</td> </div>
</tr> </div>
</table>
</TabPanel> </TabPanel>
@if (_themeSettingsType != null) @if (_themeSettingsType != null)
{ {
@ -162,8 +151,9 @@
</TabPanel> </TabPanel>
} }
</TabStrip> </TabStrip>
<button type="button" class="btn btn-success" @onclick="SavePage">@Localizer["Save"]</button> <button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@Localizer["Cancel"]</button> <button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
</form>
@code { @code {
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
@ -175,11 +165,12 @@
private string _name; private string _name;
private string _title; private string _title;
private string _path = string.Empty; private string _path = string.Empty;
private string _parentid; private string _parentid = "-1";
private string _insert = ">>"; private string _insert = ">>";
private List<Page> _children; private List<Page> _children;
private int _childid = -1; private int _childid = -1;
private string _isnavigation = "True"; private string _isnavigation = "True";
private string _isclickable = "True";
private string _url; private string _url;
private string _ispersonalizable = "False"; private string _ispersonalizable = "False";
private string _themetype = string.Empty; private string _themetype = string.Empty;
@ -191,6 +182,8 @@
private object _themeSettings; private object _themeSettings;
private RenderFragment ThemeSettingsComponent { get; set; } private RenderFragment ThemeSettingsComponent { get; set; }
private bool _refresh = false; private bool _refresh = false;
private ElementReference form;
private bool validated = false;
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
@ -209,7 +202,7 @@
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Initializing Page {Error}", ex.Message); await logger.LogError(ex, "Error Initializing Page {Error}", ex.Message);
AddModuleMessage(Localizer["Error Initializing Page"], MessageType.Error); AddModuleMessage(Localizer["Error.Page.Initialize"], MessageType.Error);
} }
} }
@ -244,7 +237,7 @@
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Loading Child Pages For Parent {PageId} {Error}", _parentid, ex.Message); 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);
} }
} }
@ -261,7 +254,7 @@
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Loading Pane Layouts For Theme {ThemeType} {Error}", _themetype, ex.Message); 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);
} }
} }
@ -286,17 +279,21 @@
} }
private async Task SavePage() private async Task SavePage()
{
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{ {
Page page = null; Page page = null;
try try
{ {
if (!string.IsNullOrEmpty(_name) && !string.IsNullOrEmpty(_themetype) && _containertype != "-") if (!string.IsNullOrEmpty(_themetype) && _containertype != "-")
{ {
page = new Page(); page = new Page();
page.SiteId = PageState.Page.SiteId; page.SiteId = PageState.Page.SiteId;
page.Name = _name; page.Name = _name;
page.Title = _title; page.Title = _title;
if (_path == "") if (string.IsNullOrEmpty(_path))
{ {
_path = _name; _path = _name;
} }
@ -306,7 +303,7 @@
_path = _path.Substring(_path.LastIndexOf("/") + 1); _path = _path.Substring(_path.LastIndexOf("/") + 1);
} }
if (string.IsNullOrEmpty(_parentid)) if (_parentid == "-1")
{ {
page.ParentId = null; page.ParentId = null;
page.Path = Utilities.GetFriendlyUrl(_path); page.Path = Utilities.GetFriendlyUrl(_path);
@ -325,9 +322,15 @@
} }
} }
if(PagePathIsDeleted(page.Path, page.SiteId, _pageList))
{
AddModuleMessage(string.Format(Localizer["Message.Page.Deleted"], _path), MessageType.Warning);
return;
}
if (!PagePathIsUnique(page.Path, page.SiteId, _pageList)) 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); AddModuleMessage(string.Format(Localizer["Message.Page.Exists"], _path), MessageType.Warning);
return; return;
} }
@ -351,6 +354,7 @@
} }
page.IsNavigation = (_isnavigation == null ? true : Boolean.Parse(_isnavigation)); page.IsNavigation = (_isnavigation == null ? true : Boolean.Parse(_isnavigation));
page.IsClickable = (_isclickable == null ? true : Boolean.Parse(_isclickable));
page.Url = _url; page.Url = _url;
page.ThemeType = (_themetype != "-") ? _themetype : string.Empty; page.ThemeType = (_themetype != "-") ? _themetype : string.Empty;
if (!string.IsNullOrEmpty(page.ThemeType) && page.ThemeType == PageState.Site.DefaultThemeType) if (!string.IsNullOrEmpty(page.ThemeType) && page.ThemeType == PageState.Site.DefaultThemeType)
@ -382,14 +386,19 @@
} }
else else
{ {
AddModuleMessage(Localizer["You Must Provide Page Name, Theme, and Container"], MessageType.Warning); AddModuleMessage(Localizer["Message.Required.PageInfo"], MessageType.Warning);
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Saving Page {Page} {Error}", page, ex.Message); await logger.LogError(ex, "Error Saving Page {Page} {Error}", page, ex.Message);
AddModuleMessage(Localizer["Error Saving Page"], MessageType.Error); AddModuleMessage(Localizer["Error.Page.Save"], MessageType.Error);
}
}
else
{
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
} }
} }
@ -409,4 +418,9 @@
{ {
return !existingPages.Any(page => page.SiteId == siteId && page.Path == pagePath); 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

@ -5,26 +5,24 @@
@inject IPageService PageService @inject IPageService PageService
@inject IThemeService ThemeService @inject IThemeService ThemeService
@inject IStringLocalizer<Edit> Localizer @inject IStringLocalizer<Edit> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<TabStrip Refresh="@_refresh"> <TabStrip Refresh="@_refresh">
<TabPanel Name="Settings" ResourceKey="Settings"> <TabPanel Name="Settings" ResourceKey="Settings">
@if (_themeList != null) @if (_themeList != null)
{ {
<table class="table table-borderless"> <div class="container">
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="name" HelpText="Enter the page name" ResourceKey="Name">Name: </Label>
<Label For="Name" HelpText="Enter the page name" ResourceKey="Name">Name: </Label> <div class="col-sm-9">
</td> <input id="name" class="form-control" @bind="@_name" maxlength="50" required />
<td> </div>
<input id="Name" class="form-control" @bind="@_name" /> </div>
</td> <div class="row mb-1 align-items-center">
</tr> <Label Class="col-sm-3" For="parent" HelpText="Select the parent for the page in the site hierarchy" ResourceKey="Parent">Parent: </Label>
<tr> <div class="col-sm-9">
<td> <select id="parent" class="form-select" value="@_parentid" @onchange="(e => ParentChanged(e))" required>
<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" value="@_parentid" @onchange="(e => ParentChanged(e))">
<option value="-1">&lt;@Localizer["SiteRoot"]&gt;</option> <option value="-1">&lt;@Localizer["SiteRoot"]&gt;</option>
@foreach (Page page in _pageList) @foreach (Page page in _pageList)
{ {
@ -34,17 +32,15 @@
} }
} }
</select> </select>
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <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>
<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> <div class="col-sm-9">
</td> <select id="move" class="form-select" @bind="@_insert" required>
<td>
<select id="Move" class="form-control" @bind="@_insert">
@if (_parentid == _currentparentid) @if (_parentid == _currentparentid)
{ {
<option value="=">&lt;@Localizer["Maintain Current Location"]&gt;</option> <option value="=">&lt;@Localizer["ThisLocation.Keep"]&gt;</option>
} }
<option value="<<">@Localizer["ToBeginning"]</option> <option value="<<">@Localizer["ToBeginning"]</option>
@if (_children != null && _children.Count > 0) @if (_children != null && _children.Count > 0)
@ -56,101 +52,94 @@
</select> </select>
@if (_children != null && _children.Count > 0 && (_insert == "<" || _insert == ">")) @if (_children != null && _children.Count > 0 && (_insert == "<" || _insert == ">"))
{ {
<select class="form-control" @bind="@_childid"> <select class="form-select" @bind="@_childid">
<option value="-1">&lt;@Localizer["Select Page"]&gt;</option> <option value="-1">&lt;@Localizer["Page.Select"]&gt;</option>
@foreach (Page page in _children) @foreach (Page page in _children)
{ {
<option value="@(page.PageId)">@(page.Name)</option> <option value="@(page.PageId)">@(page.Name)</option>
} }
</select> </select>
} }
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="navigation" HelpText="Select whether the page is part of the site navigation or hidden" ResourceKey="Navigation">Navigation? </Label>
<Label For="Navigation" HelpText="Select whether the page is part of the site navigation or hidden" ResourceKey="Navigation">Navigation? </Label> <div class="col-sm-9">
</td> <select id="navigation" class="form-select" @bind="@_isnavigation" required>
<td> <option value="True">@SharedLocalizer["Yes"]</option>
<select id="Navigation" class="form-control" @bind="@_isnavigation"> <option value="False">@SharedLocalizer["No"]</option>
<option value="True">@Localizer["Yes"]</option>
<option value="False">@Localizer["No"]</option>
</select> </select>
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="clickable" HelpText="Select whether the link in the site navigation is enabled or disabled" ResourceKey="Clickable">Clickable? </Label>
<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> <div class="col-sm-9">
</td> <select id="clickable" class="form-select" @bind="@_isclickable" required>
<td> <option value="True">@SharedLocalizer["Yes"]</option>
<input id="Path" class="form-control" @bind="@_path" /> <option value="False">@SharedLocalizer["No"]</option>
</td> </select>
</tr> </div>
<tr> </div>
<td> <div class="row mb-1 align-items-center">
<Label For="Url" HelpText="Optionally enter a url which this page should redirect to when a user navigates to it" ResourceKey="Redirect">Redirect: </Label> <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." ResourceKey="UrlPath">Url Path: </Label>
</td> <div class="col-sm-9">
<td> <input id="path" class="form-control" @bind="@_path" maxlength="256"/>
<input id="Url" class="form-control" @bind="@_url" /> </div>
</td> </div>
</tr> <div class="row mb-1 align-items-center">
</table> <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"> <Section Name="Appearance" ResourceKey="Appearance">
<table class="table table-borderless"> <div class="container">
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="title" HelpText="Optionally enter the page title. If you do not provide a page title, the page name will be used." ResourceKey="Title">Title: </Label>
<Label 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">
</td> <input id="title" class="form-control" @bind="@_title" maxlength="200"/>
<td> </div>
<input id="Title" class="form-control" @bind="@_title" /> </div>
</td> <div class="row mb-1 align-items-center">
</tr> <Label Class="col-sm-3" For="theme" HelpText="Select the theme for this page" ResourceKey="Theme">Theme: </Label>
<tr> <div class="col-sm-9">
<td> <select id="theme" class="form-select" value="@_themetype" @onchange="(e => ThemeChanged(e))" required>
<Label For="Theme" HelpText="Select the theme for this page" ResourceKey="Theme">Theme: </Label>
</td>
<td>
<select id="Theme" class="form-control" value="@_themetype" @onchange="(e => ThemeChanged(e))">
@foreach (var theme in _themes) @foreach (var theme in _themes)
{ {
<option value="@theme.TypeName">@theme.Name</option> <option value="@theme.TypeName">@theme.Name</option>
} }
</select> </select>
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="container" HelpText="Select the default container for the page" ResourceKey="DefaultContainer">Default Container: </Label>
<Label For="defaultContainer" HelpText="Select the default container for the page" ResourceKey="DefaultContainer">Default Container: </Label> <div class="col-sm-9">
</td> <select id="container" class="form-select" @bind="@_containertype" required>
<td> <option value="-">&lt;@Localizer["Container.Select"]&gt;</option>
<select id="defaultContainer" class="form-control" @bind="@_containertype">
<option value="-">&lt;@Localizer["Select Container"]&gt;</option>
@foreach (var container in _containers) @foreach (var container in _containers)
{ {
<option value="@container.TypeName">@container.Name</option> <option value="@container.TypeName">@container.Name</option>
} }
</select> </select>
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="icon" HelpText="Optionally provide an icon class name for this page which will be displayed in the site navigation" ResourceKey="Icon">Icon: </Label>
<Label 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">
</td> <input id="icon" class="form-control" @bind="@_icon" maxlength="50"/>
<td> </div>
<input id="Icon" class="form-control" @bind="@_icon" /> </div>
</td> <div class="row mb-1 align-items-center">
</tr> <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>
<tr> <div class="col-sm-9">
<td> <select id="personalizable" class="form-select" @bind="@_ispersonalizable" required>
<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> <option value="True">@SharedLocalizer["Yes"]</option>
</td> <option value="False">@SharedLocalizer["No"]</option>
<td>
<select id="Personalizable" class="form-control" @bind="@_ispersonalizable">
<option value="True">@Localizer["Yes"]</option>
<option value="False">@Localizer["No"]</option>
</select> </select>
</td> </div>
</tr> </div>
</table> </div>
</Section> </Section>
<br /><br /> <br /><br />
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon" DeletedBy="@_deletedby" DeletedOn="@_deletedon"></AuditInfo> <AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon" DeletedBy="@_deletedby" DeletedOn="@_deletedon"></AuditInfo>
@ -159,13 +148,13 @@
<TabPanel Name="Permissions" ResourceKey="Permissions"> <TabPanel Name="Permissions" ResourceKey="Permissions">
@if (_permissions != null) @if (_permissions != null)
{ {
<table class="table table-borderless"> <div class="container">
<tr> <div class="row mb-1 align-items-center">
<td>
<PermissionGrid EntityName="@EntityNames.Page" Permissions="@_permissions" @ref="_permissionGrid" /> <PermissionGrid EntityName="@EntityNames.Page" Permissions="@_permissions" @ref="_permissionGrid" />
</td>
</tr> </div>
</table> </div>
} }
</TabPanel> </TabPanel>
@if (_themeSettingsType != null) @if (_themeSettingsType != null)
@ -173,14 +162,18 @@
<TabPanel Name="ThemeSettings" Heading="Theme Settings" ResourceKey="ThemeSettings"> <TabPanel Name="ThemeSettings" Heading="Theme Settings" ResourceKey="ThemeSettings">
@ThemeSettingsComponent @ThemeSettingsComponent
</TabPanel> </TabPanel>
<br />
} }
</TabStrip> </TabStrip>
<button type="button" class="btn btn-success" @onclick="SavePage">@Localizer["Save"]</button> <button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@Localizer["Cancel"]</button> <button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
</form>
@code { @code {
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
private ElementReference form;
private bool validated = false;
private List<Theme> _themeList; private List<Theme> _themeList;
private List<ThemeControl> _themes = new List<ThemeControl>(); private List<ThemeControl> _themes = new List<ThemeControl>();
private List<ThemeControl> _containers = new List<ThemeControl>(); private List<ThemeControl> _containers = new List<ThemeControl>();
@ -190,11 +183,12 @@
private string _title; private string _title;
private string _path; private string _path;
private string _currentparentid; private string _currentparentid;
private string _parentid; private string _parentid = "-1";
private string _insert = "="; private string _insert = "=";
private List<Page> _children; private List<Page> _children;
private int _childid = -1; private int _childid = -1;
private string _isnavigation; private string _isnavigation;
private string _isclickable;
private string _url; private string _url;
private string _ispersonalizable; private string _ispersonalizable;
private string _themetype; private string _themetype;
@ -238,7 +232,7 @@
if (page.ParentId == null) if (page.ParentId == null)
{ {
_parentid = string.Empty; _parentid = "-1";
} }
else else
{ {
@ -247,6 +241,7 @@
_currentparentid = _parentid; _currentparentid = _parentid;
_isnavigation = page.IsNavigation.ToString(); _isnavigation = page.IsNavigation.ToString();
_isclickable = page.IsClickable.ToString();
_url = page.Url; _url = page.Url;
_ispersonalizable = page.IsPersonalizable.ToString(); _ispersonalizable = page.IsPersonalizable.ToString();
_themetype = page.ThemeType; _themetype = page.ThemeType;
@ -275,7 +270,7 @@
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Loading Page {PageId} {Error}", _pageId, ex.Message); 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);
} }
} }
@ -318,7 +313,7 @@
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Loading Child Pages For Parent {PageId} {Error}", _parentid, ex.Message); 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);
} }
} }
@ -335,7 +330,7 @@
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Loading Pane Layouts For Theme {ThemeType} {Error}", _themetype, ex.Message); 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);
} }
} }
@ -360,19 +355,22 @@
} }
private async Task SavePage() private async Task SavePage()
{
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{ {
Page page = null; Page page = null;
try try
{ {
if (_name != string.Empty && !string.IsNullOrEmpty(_themetype) && _containertype != "-") if (!string.IsNullOrEmpty(_themetype) && _containertype != "-")
{ {
page = PageState.Pages.FirstOrDefault(item => item.PageId == _pageId); page = PageState.Pages.FirstOrDefault(item => item.PageId == _pageId);
string currentPath = page.Path; string currentPath = page.Path;
page.Name = _name; page.Name = _name;
page.Title = _title; page.Title = _title;
if (_path == "" && _name.ToLower() != "home") if (string.IsNullOrEmpty(_path) && _name.ToLower() != "home")
if (_path == string.Empty && _name.ToLower() != "home")
{ {
_path = _name; _path = _name;
} }
@ -380,7 +378,7 @@
{ {
_path = _path.Substring(_path.LastIndexOf("/") + 1); _path = _path.Substring(_path.LastIndexOf("/") + 1);
} }
if (string.IsNullOrEmpty(_parentid) || _parentid == "-1") if (_parentid == "-1")
{ {
page.ParentId = null; page.ParentId = null;
page.Path = Utilities.GetFriendlyUrl(_path); page.Path = Utilities.GetFriendlyUrl(_path);
@ -401,7 +399,7 @@
if (!PagePathIsUnique(page.Path, page.SiteId, page.PageId, _pageList)) 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); AddModuleMessage(string.Format(Localizer["Mesage.Page.PathExists"], _path), MessageType.Warning);
return; return;
} }
@ -427,6 +425,7 @@
} }
} }
page.IsNavigation = (_isnavigation == null || Boolean.Parse(_isnavigation)); page.IsNavigation = (_isnavigation == null || Boolean.Parse(_isnavigation));
page.IsClickable = (_isclickable == null ? true : Boolean.Parse(_isclickable));
page.Url = _url; page.Url = _url;
page.ThemeType = (_themetype != "-") ? _themetype : string.Empty; page.ThemeType = (_themetype != "-") ? _themetype : string.Empty;
if (!string.IsNullOrEmpty(page.ThemeType) && page.ThemeType == PageState.Site.DefaultThemeType) if (!string.IsNullOrEmpty(page.ThemeType) && page.ThemeType == PageState.Site.DefaultThemeType)
@ -481,13 +480,18 @@
} }
else else
{ {
AddModuleMessage(Localizer["You Must Provide Page Name, Theme, and Container"], MessageType.Warning); AddModuleMessage(Localizer["Message.Required.PageInfo"], MessageType.Warning);
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Saving Page {Page} {Error}", page, ex.Message); await logger.LogError(ex, "Error Saving Page {Page} {Error}", page, ex.Message);
AddModuleMessage(Localizer["Error Saving Page"], MessageType.Error); AddModuleMessage(Localizer["Error.Page.Save"], MessageType.Error);
}
}
else
{
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
} }
} }

View File

@ -3,6 +3,7 @@
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject IPageService PageService @inject IPageService PageService
@inject IStringLocalizer<Index> Localizer @inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (PageState.Pages != null) @if (PageState.Pages != null)
{ {
@ -12,11 +13,11 @@
<Header> <Header>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th>@Localizer["Name"]</th> <th>@SharedLocalizer["Name"]</th>
</Header> </Header>
<Row> <Row>
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.PageId.ToString())" ResourceKey="EditPage" /></td> <td><ActionLink Action="Edit" Parameters="@($"id=" + context.PageId.ToString())" ResourceKey="EditPage" /></td>
<td><ActionDialog Header="Delete Page" Message="@Localizer["Are You Sure You Wish To 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>@(new string('-', context.Level * 2))@(context.Name)</td> <td>@(new string('-', context.Level * 2))@(context.Name)</td>
</Row> </Row>
</Pager> </Pager>
@ -38,7 +39,7 @@
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Deleting Page {Page} {Error}", page, ex.Message); 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);
} }
} }
} }

View File

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

View File

@ -2,10 +2,11 @@
@inherits ModuleBase @inherits ModuleBase
@inject IProfileService ProfileService @inject IProfileService ProfileService
@inject IStringLocalizer<Index> Localizer @inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_profiles == null) @if (_profiles == null)
{ {
<p><em>@Localizer["Loading..."]</em></p> <p><em>@SharedLocalizer["Loading"]</em></p>
} }
else else
{ {
@ -15,11 +16,11 @@ else
<Header> <Header>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th>@Localizer["Name"]</th> <th>@SharedLocalizer["Name"]</th>
</Header> </Header>
<Row> <Row>
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.ProfileId.ToString())" ResourceKey="EditProfile" /></td> <td><ActionLink Action="Edit" Parameters="@($"id=" + context.ProfileId.ToString())" ResourceKey="EditProfile" /></td>
<td><ActionDialog Header="Delete Profile" Message="@Localizer["Are You Sure You Wish To Delete {0}?", 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> <td>@context.Name</td>
</Row> </Row>
</Pager> </Pager>
@ -42,7 +43,7 @@ else
await ProfileService.DeleteProfileAsync(profileId); await ProfileService.DeleteProfileAsync(profileId);
await logger.LogInformation("Profile Deleted {ProfileId}", profileId); await logger.LogInformation("Profile Deleted {ProfileId}", profileId);
AddModuleMessage(Localizer["Profile Deleted"], MessageType.Success); AddModuleMessage(Localizer["Success.Profile.Delete"], MessageType.Success);
await GetProfilesAsync(); await GetProfilesAsync();
@ -51,7 +52,7 @@ else
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Deleting Profile {ProfileId} {Error}", profileId, ex.Message); 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);
} }
} }

View File

@ -5,13 +5,14 @@
@inject IModuleService ModuleService @inject IModuleService ModuleService
@inject IPageService PageService @inject IPageService PageService
@inject IStringLocalizer<Index> Localizer @inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<TabStrip> <TabStrip>
<TabPanel Name="Pages" ResourceKey="Pages"> <TabPanel Name="Pages" ResourceKey="Pages">
@if (_pages == null) @if (_pages == null)
{ {
<br /> <br />
<p>@Localizer["No Deleted Pages"]</p> <p>@Localizer["NoPage.Deleted"]</p>
} }
else else
{ {
@ -19,13 +20,13 @@
<Header> <Header>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th>@Localizer["Name"]</th> <th>@SharedLocalizer["Name"]</th>
<th>@Localizer["DeletedBy"]</th> <th>@Localizer["DeletedBy"]</th>
<th>@Localizer["DeletedOn"]</th> <th>@Localizer["DeletedOn"]</th>
</Header> </Header>
<Row> <Row>
<td><button @onclick="@(() => RestorePage(context))" class="btn btn-info" title="Restore">Restore</button></td> <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.Name</td>
<td>@context.DeletedBy</td> <td>@context.DeletedBy</td>
<td>@context.DeletedOn</td> <td>@context.DeletedOn</td>
@ -43,7 +44,7 @@
@if (_modules == null) @if (_modules == null)
{ {
<br /> <br />
<p>@Localizer["No Deleted Modules"]</p> <p>@Localizer["NoModule.Deleted"]</p>
} }
else else
{ {
@ -58,7 +59,7 @@
</Header> </Header>
<Row> <Row>
<td><button @onclick="@(() => RestoreModule(context))" class="btn btn-info" title="Restore">@Localizer["Restore"]</button></td> <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>@PageState.Pages.Find(item => item.PageId == context.PageId).Name</td>
<td>@context.Title</td> <td>@context.Title</td>
<td>@context.DeletedBy</td> <td>@context.DeletedBy</td>
@ -91,7 +92,7 @@
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Loading Deleted Pages Or Modules {Error}", ex.Message); 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);
} }
} }
@ -118,7 +119,7 @@
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Restoring Deleted Page {Page} {Error}", page, ex.Message); 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);
} }
} }
@ -175,7 +176,7 @@
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Restoring Deleted Module {Module} {Error}", module, ex.Message); 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);
} }
} }
@ -199,7 +200,7 @@
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Permanently Deleting Module {Module} {Error}", module, ex.Message); 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);
} }
} }
@ -226,7 +227,7 @@
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Permanently Deleting Modules {Error}", ex.Message); await logger.LogError(ex, "Error Permanently Deleting Modules {Error}", ex.Message);
AddModuleMessage(Localizer["Error Permanently Deleting Modules"], MessageType.Error); AddModuleMessage(Localizer["Error.Modules.Delete"], MessageType.Error);
} }
} }
} }

View File

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

View File

@ -3,25 +3,30 @@
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject IUserService UserService @inject IUserService UserService
@inject IStringLocalizer<Index> Localizer @inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<div class="container"> <div class="container">
<div class="form-group"> <div class="form-group">
<label for="Username" class="control-label">@Localizer["Username:"] </label> <label for="Username" class="control-label">@SharedLocalizer["Username"] </label>
<input type="text" class="form-control" placeholder="Username" @bind="@_username" readonly id="Username" /> <input type="text" class="form-control" placeholder="Username" @bind="@_username" readonly id="Username" />
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="Password" class="control-label">@Localizer["Password:"] </label> <label for="Password" class="control-label">@SharedLocalizer["Password"] </label>
<input type="password" class="form-control" placeholder="Password" @bind="@_password" id="Password"/> <input type="password" class="form-control" placeholder="Password" @bind="@_password" id="Password" required />
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="Confirm" class="control-label">@Localizer["Confirm Password:"] </label> <label for="Confirm" class="control-label">@Localizer["Password.Confirm"] </label>
<input type="password" class="form-control" placeholder="Password" @bind="@_confirm" id="Confirm"/> <input type="password" class="form-control" placeholder="Password" @bind="@_confirm" id="Confirm" required />
</div> </div>
<button type="button" class="btn btn-primary" @onclick="Reset">@Localizer["Reset Password"]</button> <button type="button" class="btn btn-primary" @onclick="Reset">@Localizer["Password.Reset"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@Localizer["Cancel"]</button> <button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
</div> </div>
</form>
@code { @code {
private ElementReference form;
private bool validated = false;
private string _username = string.Empty; private string _username = string.Empty;
private string _password = string.Empty; private string _password = string.Empty;
private string _confirm = string.Empty; private string _confirm = string.Empty;
@ -41,6 +46,10 @@
} }
private async Task Reset() private async Task Reset()
{
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{ {
try try
{ {
@ -64,23 +73,28 @@
else else
{ {
await logger.LogError("Error Resetting User Password {Username}", _username); 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["Error.Password.ResetInfo"], MessageType.Error);
} }
} }
else else
{ {
AddModuleMessage(Localizer["Passwords Entered Do Not Match"], MessageType.Warning); AddModuleMessage(Localizer["Message.Password.NoMatch"], MessageType.Warning);
} }
} }
else else
{ {
AddModuleMessage(Localizer["You Must Provide A Username, Password, and Email Address"], MessageType.Warning); AddModuleMessage(Localizer["Message.Required.UserInfo"], MessageType.Warning);
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Resetting User Password {Username} {Error}", _username, ex.Message); await logger.LogError(ex, "Error Resetting User Password {Username} {Error}", _username, ex.Message);
AddModuleMessage(Localizer["Error Resetting User Password"], MessageType.Error); AddModuleMessage(Localizer["Error.Password.Reset"], MessageType.Error);
}
}
else
{
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
} }
} }

View File

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

View File

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

View File

@ -2,10 +2,11 @@
@inherits ModuleBase @inherits ModuleBase
@inject IRoleService RoleService @inject IRoleService RoleService
@inject IStringLocalizer<Index> Localizer @inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_roles == null) @if (_roles == null)
{ {
<p><em>@Localizer["Loading..."]</em></p> <p><em>@SharedLocalizer["Loading"]</em></p>
} }
else 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 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> </Header>
<Row> <Row>
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.RoleId.ToString())" Disabled="@(context.IsSystem)" ResourceKey="Edit" /></td> <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><ActionLink Action="Users" Parameters="@($"id=" + context.RoleId.ToString())" ResourceKey="Users" /></td>
<td>@context.Name</td> <td>@context.Name</td>
</Row> </Row>
@ -33,6 +34,27 @@ else
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
protected override async Task OnParametersSetAsync() protected override async Task OnParametersSetAsync()
{
await GetRoles();
}
private async Task DeleteRole(Role role)
{
try
{
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.DeleteRole"], MessageType.Error);
}
}
private async Task GetRoles()
{ {
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{ {
@ -44,19 +66,4 @@ else
_roles = await RoleService.GetRolesAsync(PageState.Site.SiteId); _roles = await RoleService.GetRolesAsync(PageState.Site.SiteId);
} }
} }
private async Task DeleteRole(Role role)
{
try
{
await RoleService.DeleteRoleAsync(role.RoleId);
await logger.LogInformation("Role Deleted {Role}", role);
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Deleting Role {Role} {Error}", role, ex.Message);
AddModuleMessage(Localizer["Error Deleting Role"], MessageType.Error);
}
}
} }

View File

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

View File

@ -3,235 +3,232 @@
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject ISiteService SiteService @inject ISiteService SiteService
@inject ITenantService TenantService @inject ITenantService TenantService
@inject IDatabaseService DatabaseService
@inject IAliasService AliasService @inject IAliasService AliasService
@inject IThemeService ThemeService @inject IThemeService ThemeService
@inject ISettingService SettingService @inject ISettingService SettingService
@inject IStringLocalizer<Index> Localizer @inject IStringLocalizer<Index> Localizer
@inject INotificationService NotificationService @inject INotificationService NotificationService
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_initialized) @if (_initialized)
{ {
<table class="table table-borderless"> <form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<tr> <div class="container">
<td> <div class="row mb-1 align-items-center">
<Label For="name" HelpText="Enter the site name" ResourceKey="Name">Name: </Label> <Label Class="col-sm-3" For="name" HelpText="Enter the site name" ResourceKey="Name">Name: </Label>
</td> <div class="col-sm-9">
<td> <input id="name" class="form-control" @bind="@_name" maxlength="200" required />
<input id="name" class="form-control" @bind="@_name" /> </div>
</td> </div>
</tr> <div class="row mb-1 align-items-center">
<tr> <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>
<td> <div class="col-sm-9">
<Label For="tenant" HelpText="Enter the tenant for the site" ResourceKey="Tenant">Tenant: </Label> @if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
</td> {
<td> <textarea id="alias" class="form-control" @bind="@_urls" rows="3" required></textarea>
<input id="tenant" class="form-control" @bind="@_tenant" readonly /> }
</td> else
</tr> {
<tr> <textarea id="alias" class="form-control" @bind="@_urls" rows="3" readonly></textarea>
<td> }
<Label 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>
</td> </div>
<td> <div class="row mb-1 align-items-center">
<textarea id="alias" class="form-control" @bind="@_urls" rows="3"></textarea> <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>
</td> <div class="col-sm-9">
</tr> <select id="allowRegister" class="form-select" @bind="@_allowregistration" required>
<tr> <option value="True">@SharedLocalizer["Yes"]</option>
<td> <option value="False">@SharedLocalizer["No"]</option>
<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> </select>
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="isDeleted" HelpText="Is this site deleted?" ResourceKey="IsDeleted">Is Deleted? </Label>
<Label For="isDeleted" HelpText="Is this site deleted?" ResourceKey="IsDeleted">Is Deleted? </Label> <div class="col-sm-9">
</td> <select id="isDeleted" class="form-select" @bind="@_isdeleted" required>
<td> <option value="True">@SharedLocalizer["Yes"]</option>
<select id="isDeleted" class="form-control" @bind="@_isdeleted"> <option value="False">@SharedLocalizer["No"]</option>
<option value="True">@Localizer["Yes"]</option>
<option value="False">@Localizer["No"]</option>
</select> </select>
</td> </div>
</tr> </div>
</table> </div>
<Section Name="Appearance" Heading="Appearance" ResourceKey="Appearance"> <Section Name="Appearance" Heading="Appearance" ResourceKey="Appearance">
<table class="table table-borderless"> <div class="container">
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="logo" HelpText="Specify a logo for the site" ResourceKey="Logo">Logo: </Label>
<Label For="logo" HelpText="Specify a logo for the site" ResourceKey="Logo">Logo: </Label> <div class="col-sm-9">
</td>
<td>
<FileManager FileId="@_logofileid" Filter="@Constants.ImageFiles" @ref="_logofilemanager" /> <FileManager FileId="@_logofileid" Filter="@Constants.ImageFiles" @ref="_logofilemanager" />
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="favicon" HelpText="Specify a Favicon" ResourceKey="FavoriteIcon">Favicon: </Label>
<Label For="favicon" HelpText="Specify a Favicon" ResourceKey="FavoriteIcon">Favicon: </Label> <div class="col-sm-9">
</td>
<td>
<FileManager FileId="@_faviconfileid" Filter="ico" @ref="_faviconfilemanager" /> <FileManager FileId="@_faviconfileid" Filter="ico" @ref="_faviconfilemanager" />
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="defaultTheme" HelpText="Select the sites default theme" ResourceKey="DefaultTheme">Default Theme: </Label>
<Label For="defaultTheme" HelpText="Select the sites default theme" ResourceKey="DefaultTheme">Default Theme: </Label> <div class="col-sm-9">
</td> <select id="defaultTheme" class="form-select" value="@_themetype" @onchange="(e => ThemeChanged(e))" required>
<td> <option value="-">&lt;@Localizer["Theme.Select"]&gt;</option>
<select id="defaultTheme" class="form-control" value="@_themetype" @onchange="(e => ThemeChanged(e))">
<option value="-">&lt;@Localizer["Select Theme"]&gt;</option>
@foreach (var theme in _themes) @foreach (var theme in _themes)
{ {
<option value="@theme.TypeName">@theme.Name</option> <option value="@theme.TypeName">@theme.Name</option>
} }
</select> </select>
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="defaultContainer" HelpText="Select the default container for the site" ResourceKey="DefaultContainer">Default Container: </Label>
<Label For="defaultContainer" HelpText="Select the default container for the site" ResourceKey="DefaultContainer">Default Container: </Label> <div class="col-sm-9">
</td> <select id="defaultContainer" class="form-select" @bind="@_containertype" required>
<td> <option value="-">&lt;@Localizer["Container.Select"]&gt;</option>
<select id="defaultContainer" class="form-control" @bind="@_containertype">
<option value="-">&lt;@Localizer["Select Container"]&gt;</option>
@foreach (var container in _containers) @foreach (var container in _containers)
{ {
<option value="@container.TypeName">@container.Name</option> <option value="@container.TypeName">@container.Name</option>
} }
</select> </select>
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="defaultAdminContainer" HelpText="Select the default admin container for the site" ResourceKey="DefaultAdminContainer">Default Admin Container: </Label>
<Label For="defaultAdminContainer" HelpText="Select the default admin container for the site" ResourceKey="DefaultAdminContainer">Default Admin Container: </Label> <div class="col-sm-9">
</td> <select id="defaultAdminContainer" class="form-select" @bind="@_admincontainertype" required>
<td> <option value="-">&lt;@Localizer["Container.Select"]&gt;</option>
<select id="defaultAdminContainer" class="form-control" @bind="@_admincontainertype">
<option value="-">&lt;@Localizer["Select Container"]&gt;</option>
<option value="@Constants.DefaultAdminContainer">&lt;@Localizer["DefaultAdminContainer"]&gt;</option> <option value="@Constants.DefaultAdminContainer">&lt;@Localizer["DefaultAdminContainer"]&gt;</option>
@foreach (var container in _containers) @foreach (var container in _containers)
{ {
<option value="@container.TypeName">@container.Name</option> <option value="@container.TypeName">@container.Name</option>
} }
</select> </select>
</td> </div>
</tr> </div>
</table>
</div>
</Section> </Section>
<Section Name="SMTP" Heading="SMTP Settings" ResourceKey="SMTPSettings"> <Section Name="SMTP" Heading="SMTP Settings" ResourceKey="SMTPSettings">
<table class="table table-borderless"> <div class="container">
<tr> <div class="row mb-1 align-items-center">
<td colspan="2"> <div class="col-sm-3">
<strong>@Localizer["Please Note That SMTP Requires The Notification Job To Be Enabled In Scheduled Jobs"]</strong> </div>
</td> <div class="col-sm-9">
</tr> <strong>@Localizer["Smtp.Required.EnableNotificationJob"]</strong><br />
<tr> </div>
<td> </div>
<Label For="host" HelpText="Enter the host name of the SMTP server" ResourceKey="Host">Host: </Label> <div class="row mb-1 align-items-center">
</td> <Label Class="col-sm-3" For="host" HelpText="Enter the host name of the SMTP server" ResourceKey="Host">Host: </Label>
<td> <div class="col-sm-9">
<input id="host" class="form-control" @bind="@_smtphost" /> <input id="host" class="form-control" @bind="@_smtphost" />
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <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>
<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> <div class="col-sm-9">
</td>
<td>
<input id="port" class="form-control" @bind="@_smtpport" /> <input id="port" class="form-control" @bind="@_smtpport" />
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="enabledSSl" HelpText="Specify if SSL is required for your SMTP server" ResourceKey="UseSsl">SSL Enabled: </Label>
<Label For="enabledSSl" HelpText="Specify if SSL is required for your SMTP server" ResourceKey="UseSsl">SSL Enabled: </Label> <div class="col-sm-9">
</td> <select id="enabledSSl" class="form-select" @bind="@_smtpssl" >
<td> <option value="True">@SharedLocalizer["Yes"]</option>
<select id="enabledSSl" class="form-control" @bind="@_smtpssl"> <option value="False">@SharedLocalizer["No"]</option>
<option value="True">@Localizer["Yes"]</option>
<option value="False">@Localizer["No"]</option>
</select> </select>
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="username" HelpText="Enter the username for your SMTP account" ResourceKey="SmptUsername">Username: </Label>
<Label For="username" HelpText="Enter the username for your SMTP account" ResourceKey="SmptUsername">Username: </Label> <div class="col-sm-9">
</td>
<td>
<input id="username" class="form-control" @bind="@_smtpusername" /> <input id="username" class="form-control" @bind="@_smtpusername" />
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="password" HelpText="Enter the password for your SMTP account" ResourceKey="SmtpPassword">Password: </Label>
<Label For="password" HelpText="Enter the password for your SMTP account" ResourceKey="SmtpPassword">Password: </Label> <div class="col-sm-9">
</td>
<td>
<input id="password" type="password" class="form-control" @bind="@_smtppassword" /> <input id="password" type="password" class="form-control" @bind="@_smtppassword" />
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <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>
<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> <div class="col-sm-9">
</td>
<td>
<input id="sender" class="form-control" @bind="@_smtpsender" /> <input id="sender" class="form-control" @bind="@_smtpsender" />
</td> </div>
</tr> </div>
</table> <button type="button" class="btn btn-secondary" @onclick="SendEmail">@Localizer["Smtp.TestConfig"]</button>
<button type="button" class="btn btn-secondary" @onclick="SendEmail">@Localizer["Test SMTP Configuration"]</button>
<br /><br /> <br /><br />
</div>
</Section> </Section>
<Section Name="PWA" Heading="Progressive Web Application Settings" ResourceKey="PWASettings"> <Section Name="PWA" Heading="Progressive Web Application Settings" ResourceKey="PWASettings">
<table class="table table-borderless"> <div class="container">
<tr> <div class="row mb-1 align-items-center">
<td> <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>
<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> <div class="col-sm-9">
</td> <select id="isEnabled" class="form-select" @bind="@_pwaisenabled" required>
<td> <option value="True">@SharedLocalizer["Yes"]</option>
<select id="isEnabled" class="form-control" @bind="@_pwaisenabled"> <option value="False">@SharedLocalizer["No"]</option>
<option value="True">@Localizer["Yes"]</option>
<option value="False">@Localizer["No"]</option>
</select> </select>
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <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>
<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> <div class="col-sm-9">
</td>
<td>
<FileManager FileId="@_pwaappiconfileid" Filter="png" @ref="_pwaappiconfilemanager" /> <FileManager FileId="@_pwaappiconfileid" Filter="png" @ref="_pwaappiconfilemanager" />
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <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>
<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> <div class="col-sm-9">
</td>
<td>
<FileManager FileId="@_pwasplashiconfileid" Filter="png" @ref="_pwasplashiconfilemanager" /> <FileManager FileId="@_pwasplashiconfileid" Filter="png" @ref="_pwasplashiconfilemanager" />
</td> </div>
</tr> </div>
</table> </div>
</Section> </Section>
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
<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 /> <br />
<button type="button" class="btn btn-success" @onclick="SaveSite">@Localizer["Save"]</button> <button type="button" class="btn btn-success" @onclick="SaveSite">@SharedLocalizer["Save"]</button>
<ActionDialog Header="Delete Site" Message="@Localizer["Are You Sure You Wish To Delete This Site?"]" Action="Delete" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await DeleteSite())" ResourceKey="DeleteSite" /> <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 />
<br /> <br />
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon" DeletedBy="@_deletedby" DeletedOn="@_deletedon"></AuditInfo> <AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon" DeletedBy="@_deletedby" DeletedOn="@_deletedon"></AuditInfo>
</form>
} }
@code { @code {
private ElementReference form;
private bool validated = false;
private bool _initialized = false; private bool _initialized = false;
private List<Theme> _themeList; private List<Theme> _themeList;
private List<ThemeControl> _themes = new List<ThemeControl>(); private List<ThemeControl> _themes = new List<ThemeControl>();
private List<ThemeControl> _containers = new List<ThemeControl>(); private List<ThemeControl> _containers = new List<ThemeControl>();
private string _name = string.Empty; private string _name = string.Empty;
private List<Tenant> _tenantList;
private string _tenant = string.Empty;
private List<Alias> _aliasList; private List<Alias> _aliasList;
private string _urls = string.Empty; private string _urls = string.Empty;
private int _logofileid = -1; private int _logofileid = -1;
@ -253,6 +250,9 @@
private FileManager _pwaappiconfilemanager; private FileManager _pwaappiconfilemanager;
private int _pwasplashiconfileid = -1; private int _pwasplashiconfileid = -1;
private FileManager _pwasplashiconfilemanager; private FileManager _pwasplashiconfilemanager;
private string _tenant = string.Empty;
private string _database = string.Empty;
private string _connectionstring = string.Empty;
private string _createdby; private string _createdby;
private DateTime _createdon; private DateTime _createdon;
private string _modifiedby; private string _modifiedby;
@ -268,18 +268,24 @@
try try
{ {
_themeList = await ThemeService.GetThemesAsync(); _themeList = await ThemeService.GetThemesAsync();
_aliasList = await AliasService.GetAliasesAsync();
Site site = await SiteService.GetSiteAsync(PageState.Site.SiteId); Site site = await SiteService.GetSiteAsync(PageState.Site.SiteId);
if (site != null) if (site != null)
{ {
_name = site.Name; _name = site.Name;
_tenantList = await TenantService.GetTenantsAsync(); _allowregistration = site.AllowRegistration.ToString();
_tenant = _tenantList.Find(item => item.TenantId == site.TenantId).Name; _isdeleted = site.IsDeleted.ToString();
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
_aliasList = await AliasService.GetAliasesAsync();
foreach (Alias alias in _aliasList.Where(item => item.SiteId == site.SiteId && item.TenantId == site.TenantId).ToList()) foreach (Alias alias in _aliasList.Where(item => item.SiteId == site.SiteId && item.TenantId == site.TenantId).ToList())
{ {
_urls += alias.Name + ","; _urls += alias.Name + ",";
} }
_urls = _urls.Substring(0, _urls.Length - 1); _urls = _urls.Substring(0, _urls.Length - 1);
}
if (site.LogoFileId != null) if (site.LogoFileId != null)
{ {
_logofileid = site.LogoFileId.Value; _logofileid = site.LogoFileId.Value;
@ -295,7 +301,6 @@
_containers = ThemeService.GetContainerControls(_themeList, _themetype); _containers = ThemeService.GetContainerControls(_themeList, _themetype);
_containertype = (!string.IsNullOrEmpty(site.DefaultContainerType)) ? site.DefaultContainerType : Constants.DefaultContainer; _containertype = (!string.IsNullOrEmpty(site.DefaultContainerType)) ? site.DefaultContainerType : Constants.DefaultContainer;
_admincontainertype = (!string.IsNullOrEmpty(site.AdminContainerType)) ? site.AdminContainerType : Constants.DefaultAdminContainer; _admincontainertype = (!string.IsNullOrEmpty(site.AdminContainerType)) ? site.AdminContainerType : Constants.DefaultAdminContainer;
_allowregistration = site.AllowRegistration.ToString();
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId); var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
_smtphost = SettingService.GetSetting(settings, "SMTPHost", string.Empty); _smtphost = SettingService.GetSetting(settings, "SMTPHost", string.Empty);
@ -327,13 +332,25 @@
_pwasplashiconfileid = site.PwaSplashIconFileId.Value; _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; _createdby = site.CreatedBy;
_createdon = site.CreatedOn; _createdon = site.CreatedOn;
_modifiedby = site.ModifiedBy; _modifiedby = site.ModifiedBy;
_modifiedon = site.ModifiedOn; _modifiedon = site.ModifiedOn;
_deletedby = site.DeletedBy; _deletedby = site.DeletedBy;
_deletedon = site.DeletedOn; _deletedon = site.DeletedOn;
_isdeleted = site.IsDeleted.ToString();
_initialized = true; _initialized = true;
} }
@ -365,17 +382,23 @@
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Loading Pane Layouts For Theme {ThemeType} {Error}", _themetype, ex.Message); 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() private async Task SaveSite()
{
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{ {
try try
{ {
if (_name != string.Empty && _urls != string.Empty && _themetype != "-" && _containertype != "-") if (_name != string.Empty && _urls != string.Empty && _themetype != "-" && _containertype != "-")
{ {
var unique = true; var unique = true;
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
foreach (string name in _urls.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) 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)) if (_aliasList.Exists(item => item.Name == name && item.SiteId != PageState.Alias.SiteId && item.TenantId != PageState.Alias.TenantId))
@ -383,6 +406,7 @@
unique = false; unique = false;
} }
} }
}
if (unique) if (unique)
{ {
@ -392,34 +416,30 @@
bool refresh = (site.DefaultThemeType != _themetype || site.DefaultContainerType != _containertype); bool refresh = (site.DefaultThemeType != _themetype || site.DefaultContainerType != _containertype);
site.Name = _name; site.Name = _name;
site.AllowRegistration = (_allowregistration == null ? true : Boolean.Parse(_allowregistration));
site.IsDeleted = (_isdeleted == null ? true : Boolean.Parse(_isdeleted));
site.LogoFileId = null; site.LogoFileId = null;
var logofileid = _logofilemanager.GetFileId(); var logofileid = _logofilemanager.GetFileId();
if (logofileid != -1) if (logofileid != -1)
{ {
site.LogoFileId = logofileid; site.LogoFileId = logofileid;
} }
var faviconFieldId = _faviconfilemanager.GetFileId(); var faviconFieldId = _faviconfilemanager.GetFileId();
if (faviconFieldId != -1) if (faviconFieldId != -1)
{ {
site.FaviconFileId = faviconFieldId; site.FaviconFileId = faviconFieldId;
} }
site.DefaultThemeType = _themetype; site.DefaultThemeType = _themetype;
site.DefaultContainerType = _containertype; site.DefaultContainerType = _containertype;
site.AdminContainerType = _admincontainertype; 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)); site.PwaIsEnabled = (_pwaisenabled == null ? true : Boolean.Parse(_pwaisenabled));
var pwaappiconfileid = _pwaappiconfilemanager.GetFileId(); var pwaappiconfileid = _pwaappiconfilemanager.GetFileId();
if (pwaappiconfileid != -1) if (pwaappiconfileid != -1)
{ {
site.PwaAppIconFileId = pwaappiconfileid; site.PwaAppIconFileId = pwaappiconfileid;
} }
var pwasplashiconfileid = _pwasplashiconfilemanager.GetFileId(); var pwasplashiconfileid = _pwasplashiconfilemanager.GetFileId();
if (pwasplashiconfileid != -1) if (pwasplashiconfileid != -1)
{ {
@ -428,6 +448,17 @@
site = await SiteService.UpdateSiteAsync(site); 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); var names = _urls.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
foreach (Alias alias in _aliasList.Where(item => item.SiteId == site.SiteId && item.TenantId == site.TenantId).ToList()) foreach (Alias alias in _aliasList.Where(item => item.SiteId == site.SiteId && item.TenantId == site.TenantId).ToList())
{ {
@ -448,41 +479,39 @@
await AliasService.AddAliasAsync(alias); 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); await logger.LogInformation("Site Settings Saved {Site}", site);
if (refresh) if (refresh)
{ {
NavigationManager.NavigateTo(NavigateUrl()); // refresh to show new theme or container NavigationManager.NavigateTo(NavigateUrl()); // refresh to show new theme or container
} }
else else
{ {
AddModuleMessage(Localizer["Site Settings Saved"], MessageType.Success); AddModuleMessage(Localizer["Success.Settings.SaveSite"], MessageType.Success);
} }
} }
} }
else else
{ {
AddModuleMessage(Localizer["An Alias Specified Has Already Been Used For Another Site"], MessageType.Warning); AddModuleMessage(Localizer["Message.Aliases.Taken"], MessageType.Warning);
} }
} }
else else
{ {
AddModuleMessage(Localizer["You Must Provide A Site Name, Alias, And Default Theme/Container"], MessageType.Warning); AddModuleMessage(Localizer["Message.Required.SiteName"], MessageType.Warning);
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Saving Site {SiteId} {Error}", PageState.Site.SiteId, ex.Message); await logger.LogError(ex, "Error Saving Site {SiteId} {Error}", PageState.Site.SiteId, ex.Message);
AddModuleMessage(Localizer["Error Saving Site"], MessageType.Error); AddModuleMessage(Localizer["Error.SaveSite"], MessageType.Error);
}
}
else
{
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
} }
} }
@ -497,7 +526,7 @@
await logger.LogInformation("Site Deleted {SiteId}", PageState.Site.SiteId); await logger.LogInformation("Site Deleted {SiteId}", PageState.Site.SiteId);
var aliases = await AliasService.GetAliasesAsync(); var aliases = await AliasService.GetAliasesAsync();
foreach (Alias a in aliases.Where(item => item.SiteId == PageState.Site.SiteId)) foreach (Alias a in aliases.Where(item => item.SiteId == PageState.Site.SiteId && item.TenantId == PageState.Site.TenantId))
{ {
await AliasService.DeleteAliasAsync(a.AliasId); await AliasService.DeleteAliasAsync(a.AliasId);
} }
@ -506,13 +535,13 @@
} }
else else
{ {
AddModuleMessage(Localizer["You Are Not Authorized To Delete The Site"], MessageType.Warning); AddModuleMessage(Localizer["Message.FailAuth.DeleteSite"], MessageType.Warning);
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Deleting Site {SiteId} {Error}", PageState.Site.SiteId, ex.Message); await logger.LogError(ex, "Error Deleting Site {SiteId} {Error}", PageState.Site.SiteId, ex.Message);
AddModuleMessage(Localizer["Error Deleting Site"], MessageType.Error); AddModuleMessage(Localizer["Error.DeleteSite"], MessageType.Error);
} }
} }
@ -533,18 +562,17 @@
await logger.LogInformation("Site SMTP Settings Saved"); 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.")); 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["SMTP Settings Saved And A Message Has Been Sent To The Email Address Associated To Your User Account... Please Wait A Few Minutes For Delivery. If You Do Not Receive The Email Please Review The Notification Job In Scheduled Jobs For Any Log Details."], MessageType.Info); AddModuleMessage(Localizer["Info.Smtp.SaveSettings"], MessageType.Info);
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Testing SMTP Configuration"); await logger.LogError(ex, "Error Testing SMTP Configuration");
AddModuleMessage(Localizer["Error Testing SMTP Configuration"], MessageType.Error); AddModuleMessage(Localizer["Error.Smtp.TestConfig"], MessageType.Error);
} }
} }
else else
{ {
AddModuleMessage(Localizer["You Must Specify The SMTP Host, Port, And Sender"], MessageType.Warning); AddModuleMessage(Localizer["Message.required.Smtp"], MessageType.Warning);
} }
} }
} }

View File

@ -11,158 +11,148 @@
@inject IInstallationService InstallationService @inject IInstallationService InstallationService
@inject IDatabaseService DatabaseService @inject IDatabaseService DatabaseService
@inject IStringLocalizer<Add> Localizer @inject IStringLocalizer<Add> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_tenants == null) @if (_tenants == null)
{ {
<p><em>@Localizer["Loading..."]</em></p> <p><em>@SharedLocalizer["Loading"]</em></p>
} }
else else
{ {
<table class="table table-borderless"> <form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<tr> <div class="container">
<td> <div class="row mb-1 align-items-center">
<Label For="name" HelpText="Enter the name of the site" ResourceKey="Name">Site Name: </Label> <Label Class="col-sm-3" For="name" HelpText="Enter the name of the site" ResourceKey="Name">Site Name: </Label>
</td> <div class="col-sm-9">
<td> <input id="name" class="form-control" @bind="@_name" maxlength="200" required />
<input id="name" class="form-control" @bind="@_name" /> </div>
</td> </div>
</tr> <div class="row mb-1 align-items-center">
<tr> <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>
<td> <div class="col-sm-9">
<Label 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> <textarea id="alias" class="form-control" @bind="@_urls" rows="3" required></textarea>
</td> </div>
<td> </div>
<textarea id="alias" class="form-control" @bind="@_urls" rows="3"></textarea> <div class="row mb-1 align-items-center">
</td> <Label Class="col-sm-3" For="defaultTheme" HelpText="Select the default theme for the website" ResourceKey="DefaultTheme">Default Theme: </Label>
</tr> <div class="col-sm-9">
<tr> <select id="defaultTheme" class="form-select" @onchange="(e => ThemeChanged(e))" required>
<td> <option value="-">&lt;@Localizer["Theme.Select"]&gt;</option>
<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) @foreach (var theme in _themes)
{ {
<option value="@theme.TypeName">@theme.Name</option> <option value="@theme.TypeName">@theme.Name</option>
} }
</select> </select>
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="defaultContainer" HelpText="Select the default container for the site" ResourceKey="DefaultContainer">Default Container: </Label>
<Label For="defaultContainer" HelpText="Select the default container for the site" ResourceKey="DefaultContainer">Default Container: </Label> <div class="col-sm-9">
</td> <select id="defaultContainer" class="form-select" @bind="@_containertype" required>
<td> <option value="-">&lt;@Localizer["Container.Select"]&gt;</option>
<select id="defaultContainer" class="form-control" @bind="@_containertype">
<option value="-">&lt;@Localizer["Select Container"]&gt;</option>
@foreach (var container in _containers) @foreach (var container in _containers)
{ {
<option value="@container.TypeName">@container.Name</option> <option value="@container.TypeName">@container.Name</option>
} }
</select> </select>
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="adminContainer" HelpText="Select the admin container for the site" ResourceKey="AdminContainer">Admin Container: </Label>
<Label For="adminContainer" HelpText="Select the admin container for the site" ResourceKey="AdminContainer">Admin Container: </Label> <div class="col-sm-9">
</td> <select id="adminContainer" class="form-select" @bind="@_admincontainertype" required>
<td> <option value="-">&lt;@Localizer["Container.Select"]&gt;</option>
<select id="adminContainer" class="form-control" @bind="@_admincontainertype"> <option value="">&lt;@Localizer["DefaultContainer.Admin"]&gt;</option>
<option value="-">&lt;@Localizer["Select Container"]&gt;</option>
<option value="">&lt;@Localizer["Default Admin Container"]&gt;</option>
@foreach (var container in _containers) @foreach (var container in _containers)
{ {
<option value="@container.TypeName">@container.Name</option> <option value="@container.TypeName">@container.Name</option>
} }
</select> </select>
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="siteTemplate" HelpText="Select the site template" ResourceKey="SiteTemplate">Site Template: </Label>
<Label For="siteTemplate" HelpText="Select the site template" ResourceKey="SiteTemplate">Site Template: </Label> <div class="col-sm-9">
</td> <select id="siteTemplate" class="form-select" @bind="@_sitetemplatetype" required>
<td> <option value="-">&lt;@Localizer["SiteTemplate.Select"]&gt;</option>
<select id="siteTemplate" class="form-control" @bind="@_sitetemplatetype">
<option value="-">&lt;@Localizer["Select Site Template"]&gt;</option>
@foreach (SiteTemplate siteTemplate in _siteTemplates) @foreach (SiteTemplate siteTemplate in _siteTemplates)
{ {
<option value="@siteTemplate.TypeName">@siteTemplate.Name</option> <option value="@siteTemplate.TypeName">@siteTemplate.Name</option>
} }
</select> </select>
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="tenant" HelpText="Select the tenant for the site" ResourceKey="Tenant">Tenant: </Label>
<Label For="tenant" HelpText="Select the tenant for the site" ResourceKey="Tenant">Tenant: </Label> <div class="col-sm-9">
</td> <select id="tenant" class="form-select" @onchange="(e => TenantChanged(e))" required>
<td> <option value="-">&lt;@Localizer["Tenant.Select"]&gt;</option>
<select id="tenant" class="form-control" @onchange="(e => TenantChanged(e))"> <option value="+">&lt;@Localizer["Tenant.Add"]&gt;</option>
<option value="-">&lt;@Localizer["Select Tenant"]&gt;</option>
<option value="+">&lt;@Localizer["Create New Tenant"]&gt;</option>
@foreach (Tenant tenant in _tenants) @foreach (Tenant tenant in _tenants)
{ {
<option value="@tenant.TenantId">@tenant.Name</option> <option value="@tenant.TenantId">@tenant.Name</option>
} }
</select> </select>
</td> </div>
</tr> </div>
@if (_tenantid == "+") @if (_tenantid == "+")
{ {
<tr> <div class="row mb-1 align-items-center">
<td colspan="2">
<hr class="app-rule" /> <hr class="app-rule" />
</td> </div>
</tr> <div class="row mb-1 align-items-center">
<tr> <Label Class="col-sm-3" For="name" HelpText="Enter the name for the tenant" ResourceKey="TenantName">Tenant Name: </Label>
<td> <div class="col-sm-9">
<Label For="name" HelpText="Enter the name for the tenant" ResourceKey="TenantName">Tenant Name: </Label> <input id="name" class="form-control" @bind="@_tenantName" maxlength="100" required />
</td> </div>
<td> </div>
<input id="name" class="form-control" @bind="@_tenantName" /> <div class="row mb-1 align-items-center">
</td> <Label Class="col-sm-3" For="databaseType" HelpText="Select the database type for the tenant" ResourceKey="DatabaseType">Database Type: </Label>
</tr> <div class="col-sm-9">
<tr> <select id="databaseType" class="form-select" value="@_databaseName" @onchange="(e => DatabaseChanged(e))" required>
<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" value="@_databaseName" @onchange="(e => DatabaseChanged(e))">
@foreach (var database in _databases) @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> <option value="@database.Name">@Localizer[@database.Name]</option>
} }
}
</select> </select>
</td> </div>
</tr> </div>
if (_databaseConfigType != null) if (_databaseConfigType != null)
{ {
@DatabaseConfigComponent; @DatabaseConfigComponent;
} }
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="hostUsername" HelpText="Enter the username of the host for this site" ResourceKey="HostUsername">Host Username:</Label>
<Label For="hostUsername" HelpText="Enter the username of the host for this site" ResourceKey="HostUsername">Host Username:</Label> <div class="col-sm-9">
</td>
<td>
<input id="hostUsername" class="form-control" @bind="@_hostUserName" readonly /> <input id="hostUsername" class="form-control" @bind="@_hostUserName" readonly />
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="hostPassword" HelpText="Enter the password for the host of this site" ResourceKey="HostPassword">Host Password:</Label>
<Label For="hostPassword" HelpText="Enter the password for the host of this site" ResourceKey="HostPassword">Host Password:</Label> <div class="col-sm-9">
</td> <input id="hostPassword" type="password" class="form-control" @bind="@_hostpassword" required />
<td> </div>
<input id="hostPassword" type="password" class="form-control" @bind="@_hostpassword" /> </div>
</td>
</tr>
} }
</table> </div>
<button type="button" class="btn btn-success" @onclick="SaveSite">@Localizer["Save"]</button> <br />
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@Localizer["Cancel"]</NavLink> <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 { @code {
private List<Database> _databases; private List<Database> _databases;
private ElementReference form;
private bool validated = false;
private string _databaseName = "LocalDB"; private string _databaseName = "LocalDB";
private Type _databaseConfigType; private Type _databaseConfigType;
private object _databaseConfig; private object _databaseConfig;
@ -211,7 +201,7 @@ else
} }
catch catch
{ {
AddModuleMessage(Localizer["Error loading Database Configuration Control"], MessageType.Error); AddModuleMessage(Localizer["Error.Database.LoadConfig"], MessageType.Error);
} }
} }
@ -260,11 +250,15 @@ else
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Loading Containers For Theme {ThemeType} {Error}", _themetype, ex.Message); await logger.LogError(ex, "Error Loading Containers For Theme {ThemeType} {Error}", _themetype, ex.Message);
AddModuleMessage(Localizer["Error Loading Containers For Theme"], MessageType.Error); AddModuleMessage(Localizer["Error.Theme.LoadContainers"], MessageType.Error);
} }
} }
private async Task SaveSite() private async Task SaveSite()
{
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{ {
if (_tenantid != "-" && _name != string.Empty && _urls != string.Empty && _themetype != "-" && _containertype != "-" && _sitetemplatetype != "-") if (_tenantid != "-" && _name != string.Empty && _urls != string.Empty && _themetype != "-" && _containertype != "-" && _sitetemplatetype != "-")
{ {
@ -313,17 +307,17 @@ else
} }
else else
{ {
AddModuleMessage(Localizer["You Must Specify A Server And Database"], MessageType.Error); AddModuleMessage(Localizer["Error.Required.ServerDatabase"], MessageType.Error);
} }
} }
else else
{ {
AddModuleMessage(Localizer["Invalid Host Password"], MessageType.Error); AddModuleMessage(Localizer["Error.InvalidPassword"], MessageType.Error);
} }
} }
else else
{ {
AddModuleMessage(Localizer["Tenant Name Is Missing Or Already Exists"], MessageType.Error); AddModuleMessage(Localizer["Error.TenantName.Exists"], MessageType.Error);
} }
} }
else else
@ -365,12 +359,17 @@ else
} }
else else
{ {
AddModuleMessage(Localizer["{0} Already Used For Another Site", string.Join(", ", duplicates.ToArray())], MessageType.Warning); AddModuleMessage(string.Format(Localizer["Message.SiteName.InUse"], string.Join(", ", duplicates.ToArray())), MessageType.Warning);
} }
} }
else else
{ {
AddModuleMessage(Localizer["You Must Provide A Tenant, Site Name, Alias, Default Theme/Container, And Site Template"], MessageType.Warning); AddModuleMessage(Localizer["Message.Required.Tenant"], MessageType.Warning);
}
}
else
{
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
} }
} }
} }

View File

@ -4,6 +4,7 @@
@inject IAliasService AliasService @inject IAliasService AliasService
@inject ISiteService SiteService @inject ISiteService SiteService
@inject IStringLocalizer<Index> Localizer @inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_sites == null) @if (_sites == null)
{ {
@ -16,11 +17,13 @@ else
<Pager Items="@_sites"> <Pager Items="@_sites">
<Header> <Header>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th>@Localizer["Name"]</th> <th style="width: 1px;">&nbsp;</th>
<th>@SharedLocalizer["Name"]</th>
</Header> </Header>
<Row> <Row>
<td><NavLink class="btn btn-primary" href="@(_scheme + context.Name +"/admin/site")">@Localizer["Edit"]</NavLink></td> <td><button type="button" class="btn btn-primary" @onclick="@(async () => Edit(context.Name))">@SharedLocalizer["Edit"]</button></td>
<td><a href="@(_scheme + context.Name +"?reload")">@context.Name</a></td> <td><button type="button" class="btn btn-secondary" @onclick="@(async () => Browse(context.Name))">@Localizer["Browse"]</button></td>
<td>@context.Name</td>
</Row> </Row>
</Pager> </Pager>
} }
@ -46,4 +49,14 @@ else
} }
} }
} }
private void Edit(string name)
{
NavigationManager.NavigateTo(_scheme + name + "/admin/site/?reload");
}
private void Browse(string name)
{
NavigationManager.NavigateTo(_scheme + name + "/?reload");
}
} }

View File

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

View File

@ -3,92 +3,176 @@
@inject ISystemService SystemService @inject ISystemService SystemService
@inject IInstallationService InstallationService @inject IInstallationService InstallationService
@inject IStringLocalizer<Index> Localizer @inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<table class="table table-borderless"> <TabStrip>
<tr> <TabPanel Name="Info" Heading="Info" ResourceKey="Info">
<td> <div class="container">
<Label For="version" HelpText="Framework Version" ResourceKey="FrameworkVersion">Framework Version: </Label> <div class="row mb-1 align-items-center">
</td> <Label Class="col-sm-3" For="version" HelpText="Framework Version" ResourceKey="FrameworkVersion">Framework Version: </Label>
<td> <div class="col-sm-9">
<input id="version" class="form-control" @bind="@_version" readonly /> <input id="version" class="form-control" @bind="@_version" readonly />
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="clrversion" HelpText="Common Language Runtime Version" ResourceKey="CLRVersion">CLR Version: </Label>
<Label For="runtime" HelpText="Blazor Runtime (Server or WebAssembly)" ResourceKey="BlazorRuntime">Blazor Runtime: </Label> <div class="col-sm-9">
</td>
<td>
<input id="runtime" class="form-control" @bind="@_runtime" readonly />
</td>
</tr>
<tr>
<td>
<Label For="rendermode" HelpText="Blazor Render Mode" ResourceKey="RenderMode">Render Mode: </Label>
</td>
<td>
<input id="rendermode" class="form-control" @bind="@_rendermode" 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 /> <input id="clrversion" class="form-control" @bind="@_clrversion" readonly />
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="osversion" HelpText="Operating System Version" ResourceKey="OSVersion">OS Version: </Label>
<Label For="osversion" HelpText="Operating System Version" ResourceKey="OsVersion">OS Version: </Label> <div class="col-sm-9">
</td>
<td>
<input id="osversion" class="form-control" @bind="@_osversion" readonly /> <input id="osversion" class="form-control" @bind="@_osversion" readonly />
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="serverpath" HelpText="Server Path" ResourceKey="ServerPath">Server Path: </Label>
<Label For="serverpath" HelpText="Server Path" ResourceKey="ServerPath">Server Path: </Label> <div class="col-sm-9">
</td>
<td>
<input id="serverpath" class="form-control" @bind="@_serverpath" readonly /> <input id="serverpath" class="form-control" @bind="@_serverpath" readonly />
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="servertime" HelpText="Server Time" ResourceKey="ServerTime">Server Time: </Label>
<Label For="servertime" HelpText="Server Time" ResourceKey="ServerTime">Server Time: </Label> <div class="col-sm-9">
</td>
<td>
<input id="servertime" class="form-control" @bind="@_servertime" readonly /> <input id="servertime" class="form-control" @bind="@_servertime" readonly />
</td> </div>
</tr> </div>
</table> <div class="row mb-1 align-items-center">
<a class="btn btn-primary" href="swagger/index.html" target="_new">@Localizer["Access Framework API"]</a>&nbsp; <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" /> <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="runtime" HelpText="Blazor Runtime (Server or WebAssembly)" ResourceKey="BlazorRuntime">Blazor Runtime: </Label>
<div class="col-sm-9">
<select id="runtime" class="form-select" @bind="@_runtime">
<option value="Server">@Localizer["Server"]</option>
<option value="WebAssembly">@Localizer["WebAssembly"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="rendermode" HelpText="Blazor Server Render Mode" ResourceKey="RenderMode">Render Mode: </Label>
<div class="col-sm-9">
<select id="rendermode" class="form-select" @bind="@_rendermode">
<option value="Server">@Localizer["Server"]</option>
<option value="ServerPrerendered">@Localizer["ServerPrerendered"]</option>
</select>
</div>
</div>
<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 { @code {
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
private string _version = string.Empty; private string _version = string.Empty;
private string _runtime = string.Empty;
private string _rendermode = string.Empty;
private string _clrversion = string.Empty; private string _clrversion = string.Empty;
private string _osversion = string.Empty; private string _osversion = string.Empty;
private string _serverpath = string.Empty; private string _serverpath = string.Empty;
private string _servertime = string.Empty; private string _servertime = string.Empty;
private string _installationid = string.Empty;
private string _runtime = string.Empty;
private string _rendermode = 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() protected override async Task OnInitializedAsync()
{ {
_version = Constants.Version; _version = Constants.Version;
_runtime = PageState.Runtime.ToString();
Dictionary<string, string> systeminfo = await SystemService.GetSystemInfoAsync(); Dictionary<string, string> systeminfo = await SystemService.GetSystemInfoAsync();
if (systeminfo != null) if (systeminfo != null)
{ {
_rendermode = systeminfo["rendermode"];
_clrversion = systeminfo["clrversion"]; _clrversion = systeminfo["clrversion"];
_osversion = systeminfo["osversion"]; _osversion = systeminfo["osversion"];
_serverpath = systeminfo["serverpath"]; _serverpath = systeminfo["serverpath"];
_servertime = systeminfo["servertime"]; _servertime = systeminfo["servertime"];
_installationid = systeminfo["installationid"];
_runtime = systeminfo["runtime"];
_rendermode = systeminfo["rendermode"];
_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("runtime", _runtime);
settings.Add("rendermode", _rendermode);
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);
} }
} }
@ -98,7 +182,7 @@
{ {
ShowProgressIndicator(); ShowProgressIndicator();
var interop = new Interop(JSRuntime); var interop = new Interop(JSRuntime);
await interop.RedirectBrowser(NavigateUrl(""), 10); await interop.RedirectBrowser(NavigateUrl(""), 20);
await InstallationService.RestartAsync(); await InstallationService.RestartAsync();
} }
catch (Exception ex) catch (Exception ex)

View File

@ -5,50 +5,123 @@
@inject IThemeService ThemeService @inject IThemeService ThemeService
@inject IPackageService PackageService @inject IPackageService PackageService
@inject IStringLocalizer<Add> Localizer @inject IStringLocalizer<Add> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<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) @if (_packages != null)
{ {
<TabStrip> if (_packages.Count > 0)
@if (_packages.Count > 0)
{ {
<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>
<Pager Items="@_packages"> <Pager Items="@_packages">
<Header>
<th>@Localizer["Name"]</th>
<th>@Localizer["Version"]</th>
<th style="width: 1px;"></th>
</Header>
<Row> <Row>
<td>@context.Name</td>
<td>@context.Version</td>
<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> </td>
</Row> </Row>
</Pager> </Pager>
</TabPanel>
} }
else
{
<br />
<div class="mx-auto text-center">
@Localizer["Search.NoResults"]
</div>
}
}
</TabPanel>
<TabPanel Name="Upload" ResourceKey="Upload"> <TabPanel Name="Upload" ResourceKey="Upload">
<table class="table table-borderless"> <div class="container">
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" HelpText="Upload one or more theme packages. Once they are uploaded click Install to complete the installation." ResourceKey="Theme">Theme: </Label>
<Label 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">
</td>
<td>
<FileManager Filter="nupkg" ShowFiles="false" Folder="Packages" UploadMultiple="@true" /> <FileManager Filter="nupkg" ShowFiles="false" Folder="Packages" UploadMultiple="@true" />
</td>
</tr> </div>
</table> </div>
</div>
</TabPanel> </TabPanel>
</TabStrip> </TabStrip>
<button type="button" class="btn btn-success" @onclick="InstallThemes">@Localizer["Install"]</button> @if (_productname != "")
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@Localizer["Cancel"]</NavLink> {
<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 { @code {
private List<Package> _packages; 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; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
@ -56,9 +129,22 @@
{ {
try try
{ {
var themes = await ThemeService.GetThemesAsync(); await LoadThemes();
_packages = await PackageService.GetPackagesAsync("theme"); }
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()) foreach (Package package in _packages.ToArray())
{ {
if (themes.Exists(item => item.PackageName == package.PackageId)) if (themes.Exists(item => item.PackageName == package.PackageId))
@ -67,10 +153,94 @@
} }
} }
} }
}
private async void PriceChanged(ChangeEventArgs e)
{
try
{
_price = (string)e.Value;
_search = "";
await LoadThemes();
StateHasChanged();
}
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Loading Packages {Error}", ex.Message); await logger.LogError(ex, "Error On PriceChanged");
AddModuleMessage(Localizer["Error Loading Packages"], MessageType.Error); }
}
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, "Packages");
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 +249,11 @@
try try
{ {
await ThemeService.InstallThemesAsync(); 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) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Installing Theme"); await logger.LogError(ex, "Error Installing Theme");
} }
} }
private async Task DownloadTheme(string packageid, string version)
{
try
{
await PackageService.DownloadPackageAsync(packageid, version, "Packages");
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

@ -6,47 +6,40 @@
@inject IModuleService ModuleService @inject IModuleService ModuleService
@inject IPageModuleService PageModuleService @inject IPageModuleService PageModuleService
@inject ISettingService SettingService @inject ISettingService SettingService
@inject IStringLocalizer<Index> Localizer @inject IStringLocalizer<Create> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_templates != null) @if (_templates != null)
{ {
<table class="table table-borderless"> <div class="container">
<tr> <div class="row mb-1 align-items-center">
<td> <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>
<Label 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">
</td>
<td>
<input id="owner" class="form-control" @bind="@_owner" /> <input id="owner" class="form-control" @bind="@_owner" />
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <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>
<Label 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">
</td>
<td>
<input id="module" class="form-control" @bind="@_theme" /> <input id="module" class="form-control" @bind="@_theme" />
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <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>
<Label 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">
</td> <select id="template" class="form-select" @onchange="(e => TemplateChanged(e))">
<td> <option value="-">&lt;@Localizer["Template.Select"]&gt;</option>
<select id="template" class="form-control" @onchange="(e => TemplateChanged(e))">
<option value="-">&lt;@Localizer["Select Template"]&gt;</option>
@foreach (Template template in _templates) @foreach (Template template in _templates)
{ {
<option value="@template.Name">@template.Title</option> <option value="@template.Name">@template.Title</option>
} }
</select> </select>
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="reference" HelpText="Select a framework reference version" ResourceKey="FrameworkReference">Framework Reference: </Label>
<Label For="reference" HelpText="Select a framework reference version" ResourceKey="FrameworkReference">Framework Reference: </Label> <div class="col-sm-9">
</td> <select id="reference" class="form-select" @bind="@_reference">
<td>
<select id="reference" class="form-control" @bind="@_reference">
@foreach (string version in _versions) @foreach (string version in _versions)
{ {
if (Version.Parse(version).CompareTo(Version.Parse(_minversion)) >= 0) if (Version.Parse(version).CompareTo(Version.Parse(_minversion)) >= 0)
@ -54,24 +47,22 @@
<option value="@(version)">@(version)</option> <option value="@(version)">@(version)</option>
} }
} }
<option value="local">@Localizer["Local Version"]</option> <option value="local">@SharedLocalizer["LocalVersion"]</option>
</select> </select>
</td> </div>
</tr> </div>
@if (!string.IsNullOrEmpty(_location)) @if (!string.IsNullOrEmpty(_location)) {
{ <div class="row mb-1 align-items-center">
<tr> <Label Class="col-sm-3" For="location" HelpText="Location where the theme will be created" ResourceKey="Location">Location: </Label>
<td> <div class="col-sm-9">
<Label For="location" HelpText="Location where the theme will be created" ResourceKey="Location">Location: </Label>
</td>
<td>
<input id="module" class="form-control" @bind="@_location" readonly /> <input id="module" class="form-control" @bind="@_location" readonly />
</td> </div>
</tr> </div>
} }
</table> </div>
<button type="button" class="btn btn-success" @onclick="CreateTheme">@Localizer["Create Theme"]</button> <br />
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@Localizer["Cancel"]</NavLink> <button type="button" class="btn btn-success" @onclick="CreateTheme">@Localizer["Theme.Create"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
} }
@code { @code {
@ -92,7 +83,7 @@
{ {
_templates = await ThemeService.GetThemeTemplatesAsync(); _templates = await ThemeService.GetThemeTemplatesAsync();
_versions = Constants.ReleaseVersions.Split(',').Where(item => Version.Parse(item).CompareTo(Version.Parse("2.0.0")) >= 0).ToArray(); _versions = Constants.ReleaseVersions.Split(',').Where(item => Version.Parse(item).CompareTo(Version.Parse("2.0.0")) >= 0).ToArray();
AddModuleMessage(Localizer["Please Note That The Theme Creator Is Only Intended To Be Used In A Development Environment"], MessageType.Info); AddModuleMessage(Localizer["Info.Theme.CreatorIntent"], MessageType.Info);
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -109,11 +100,11 @@
var theme = new Theme { Owner = _owner, Name = _theme, Template = _template, Version = _reference }; var theme = new Theme { Owner = _owner, Name = _theme, Template = _template, Version = _reference };
theme = await ThemeService.CreateThemeAsync(theme); theme = await ThemeService.CreateThemeAsync(theme);
GetLocation(); GetLocation();
AddModuleMessage(Localizer["The Source Code For Your Theme 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 Activate The Module.", NavigateUrl("admin/system")], MessageType.Success); AddModuleMessage(string.Format(Localizer["Success.Theme.Create"], NavigateUrl("admin/system")), MessageType.Success);
} }
else else
{ {
AddModuleMessage(Localizer["You Must Provide A Valid Owner Name And Theme Name ( ie. No Punctuation Or Spaces And The Values Cannot Be The Same ) And Choose A Template"], MessageType.Warning); AddModuleMessage(Localizer["Message.Required.ValidName"], MessageType.Warning);
} }
} }
catch (Exception ex) catch (Exception ex)

View File

@ -5,23 +5,25 @@
@inject IThemeService ThemeService @inject IThemeService ThemeService
@inject IPackageService PackageService @inject IPackageService PackageService
@inject IStringLocalizer<Index> Localizer @inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_themes == null) @if (_themes == null)
{ {
<p><em>Loading...</em></p> <p><em>@SharedLocalizer["Loading"]</em></p>
} }
else else
{ {
<ActionLink Action="Add" Text="Install Theme" /> <ActionLink Action="Add" Text="Install Theme" />
@((MarkupString)"&nbsp;") @((MarkupString)"&nbsp;")
<ActionLink Action="Create" Text="Create Theme" ResourceKey="CreateTheme" /> <ActionLink Action="Create" Text="Create Theme" ResourceKey="CreateTheme" Class="btn btn-secondary" />
<Pager Items="@_themes"> <Pager Items="@_themes">
<Header> <Header>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th scope="col">@Localizer["Name"]</th> <th>@SharedLocalizer["Name"]</th>
<th scope="col">@Localizer["Version"]</th> <th>@SharedLocalizer["Version"]</th>
<th>@SharedLocalizer["Expires"]</th>
<th>&nbsp;</th> <th>&nbsp;</th>
</Header> </Header>
<Row> <Row>
@ -29,15 +31,18 @@ else
<td> <td>
@if (context.AssemblyName != "Oqtane.Client") @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>
<td>@context.Name</td> <td>@context.Name</td>
<td>@context.Version</td> <td>@context.Version</td>
<td>
@((MarkupString)PurchaseLink(context.PackageName))
</td>
<td> <td>
@if (UpgradeAvailable(context.PackageName, context.Version)) @if (UpgradeAvailable(context.PackageName, context.Version))
{ {
<button type="button" class="btn btn-success" @onclick=@(async () => await DownloadTheme(context.PackageName, 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></td> <td></td>
@ -63,15 +68,36 @@ else
if (_themes == null) if (_themes == null)
{ {
await logger.LogError(ex, "Error Loading Themes {Error}", ex.Message); 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 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) private bool UpgradeAvailable(string packagename, string version)
{ {
var upgradeavailable = false; var upgradeavailable = false;
if (_packages != null) if (!string.IsNullOrEmpty(packagename) && _packages != null)
{ {
var package = _packages.Where(item => item.PackageId == packagename).FirstOrDefault(); var package = _packages.Where(item => item.PackageId == packagename).FirstOrDefault();
if (package != null) if (package != null)
@ -89,12 +115,12 @@ else
await PackageService.DownloadPackageAsync(packagename, version, "Packages"); await PackageService.DownloadPackageAsync(packagename, version, "Packages");
await logger.LogInformation("Theme Downloaded {ThemeName} {Version}", packagename, version); await logger.LogInformation("Theme Downloaded {ThemeName} {Version}", packagename, version);
await ThemeService.InstallThemesAsync(); 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) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Downloading Theme {ThemeName} {Version} {Error}", packagename, version, ex.Message); await logger.LogError(ex, "Error Downloading Theme {ThemeName} {Version} {Error}", packagename, version, ex.Message);
AddModuleMessage(Localizer["Error Downloading Theme"], MessageType.Error); AddModuleMessage(Localizer["Error.Theme.Download"], MessageType.Error);
} }
} }
@ -103,13 +129,13 @@ else
try try
{ {
await ThemeService.DeleteThemeAsync(Theme.ThemeName); await ThemeService.DeleteThemeAsync(Theme.ThemeName);
AddModuleMessage(Localizer["Theme Deleted Successfully"], MessageType.Success); AddModuleMessage(Localizer["Success.Theme.Delete"], MessageType.Success);
NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, "reload")); NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, true));
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Deleting Theme {Theme} {Error}", Theme, ex.Message); 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 IThemeService ThemeService
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject IStringLocalizer<View> Localizer @inject IStringLocalizer<View> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<table class="table table-borderless"> <div class="container">
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="name" HelpText="The name of the theme" ResourceKey="Name">Name: </Label>
<Label For="name" HelpText="The name of the theme" ResourceKey="Name">Name: </Label> <div class="col-sm-9">
</td>
<td>
<input id="name" class="form-control" @bind="@_name" disabled /> <input id="name" class="form-control" @bind="@_name" disabled />
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="themename" HelpText="The internal name of the module" ResourceKey="InternalName">Internal Name: </Label>
<Label For="themename" HelpText="The internal name of the module" ResourceKey="InternalName">Internal Name: </Label> <div class="col-sm-9">
</td>
<td>
<input id="themename" class="form-control" @bind="@_themeName" disabled /> <input id="themename" class="form-control" @bind="@_themeName" disabled />
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="version" HelpText="The version of the theme" ResourceKey="Version">Version: </Label>
<Label For="version" HelpText="The version of the theme" ResourceKey="Version">Version: </Label> <div class="col-sm-9">
</td>
<td>
<input id="version" class="form-control" @bind="@_version" disabled /> <input id="version" class="form-control" @bind="@_version" disabled />
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="owner" HelpText="The owner or creator of the theme" ResourceKey="Owner">Owner: </Label>
<Label For="owner" HelpText="The owner or creator of the theme" ResourceKey="Owner">Owner: </Label> <div class="col-sm-9">
</td>
<td>
<input id="owner" class="form-control" @bind="@_owner" disabled /> <input id="owner" class="form-control" @bind="@_owner" disabled />
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="url" HelpText="The reference url of the theme" ResourceKey="ReferenceUrl">Reference Url: </Label>
<Label For="url" HelpText="The reference url of the theme" ResourceKey="ReferenceUrl">Reference Url: </Label> <div class="col-sm-9">
</td>
<td>
<input id="url" class="form-control" @bind="@_url" disabled /> <input id="url" class="form-control" @bind="@_url" disabled />
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="contact" HelpText="The contact for the theme" ResourceKey="Contact">Contact: </Label>
<Label For="contact" HelpText="The contact for the theme" ResourceKey="Contact">Contact: </Label> <div class="col-sm-9">
</td>
<td>
<input id="contact" class="form-control" @bind="@_contact" disabled /> <input id="contact" class="form-control" @bind="@_contact" disabled />
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="license" HelpText="The license of the theme" ResourceKey="License">License: </Label>
<Label For="license" HelpText="The license of the theme" ResourceKey="License">License: </Label> <div class="col-sm-9">
</td>
<td>
<textarea id="license" class="form-control" @bind="@_license" rows="5" disabled></textarea> <textarea id="license" class="form-control" @bind="@_license" rows="5" disabled></textarea>
</td> </div>
</tr> </div>
</table> </div>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@Localizer["Cancel"]</NavLink> <NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
@code { @code {
private string _themeName = ""; private string _themeName = "";
@ -96,7 +83,7 @@
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Loading Theme {ThemeName} {Error}", _themeName, ex.Message); 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,16 +5,15 @@
@inject IPackageService PackageService @inject IPackageService PackageService
@inject IInstallationService InstallationService @inject IInstallationService InstallationService
@inject IStringLocalizer<Index> Localizer @inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_package != null)
{
<TabStrip> <TabStrip>
<TabPanel Name="Download" ResourceKey="Download"> <TabPanel Name="Download" ResourceKey="Download">
@if (_upgradeavailable) @if (_package != null && _upgradeavailable)
{ {
<ModuleMessage Type="MessageType.Info" Message="Select The Download Button To Download The Framework Upgrade Package And Then Select Upgrade"></ModuleMessage> <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))>@Localizer["Download"] @_package.Version</button> <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">@Localizer["Upgrade"]</button> <button type="button" class="btn btn-success" @onclick="Upgrade">@SharedLocalizer["Upgrade"]</button>
} }
else else
{ {
@ -23,20 +22,17 @@
</TabPanel> </TabPanel>
<TabPanel Name="Upload" ResourceKey="Upload"> <TabPanel Name="Upload" ResourceKey="Upload">
<ModuleMessage Type="MessageType.Info" Message="Upload A Framework Package (Oqtane.Framework.version.nupkg) And Then Select Upgrade"></ModuleMessage> <ModuleMessage Type="MessageType.Info" Message="Upload A Framework Package (Oqtane.Framework.version.nupkg) And Then Select Upgrade"></ModuleMessage>
<table class="table table-borderless"> <div class="container">
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" HelpText="Upload A Framework Package And Then Select Upgrade" ResourceKey="Framework">Framework: </Label>
<Label HelpText="Upload A Framework Package And Then Select Upgrade" ResourceKey="Framework">Framework: </Label> <div class="col-sm-9">
</td>
<td>
<FileManager Filter="nupkg" ShowFiles="false" Folder="Packages" /> <FileManager Filter="nupkg" ShowFiles="false" Folder="Packages" />
</td> </div>
</tr> </div>
</table> </div>
<button type="button" class="btn btn-success" @onclick="Upgrade">@Localizer["Upgrade"]</button> <button type="button" class="btn btn-success" @onclick="Upgrade">@SharedLocalizer["Upgrade"]</button>
</TabPanel> </TabPanel>
</TabStrip> </TabStrip>
}
@code { @code {
private Package _package; private Package _package;
@ -48,7 +44,7 @@
{ {
try try
{ {
List<Package> packages = await PackageService.GetPackagesAsync("framework"); List<Package> packages = await PackageService.GetPackagesAsync("framework", "", "", "");
if (packages != null) if (packages != null)
{ {
_package = packages.Where(item => item.PackageId.StartsWith(Constants.PackageId)).FirstOrDefault(); _package = packages.Where(item => item.PackageId.StartsWith(Constants.PackageId)).FirstOrDefault();
@ -72,16 +68,16 @@
{ {
try try
{ {
AddModuleMessage(Localizer["Please Be Patient While The Upgrade Is In Progress..."], MessageType.Info); AddModuleMessage(Localizer["Info.Upgrade.Wait"], MessageType.Info);
ShowProgressIndicator(); ShowProgressIndicator();
var interop = new Interop(JSRuntime); var interop = new Interop(JSRuntime);
await interop.RedirectBrowser(NavigateUrl(), 30); await interop.RedirectBrowser(NavigateUrl(), 10);
await InstallationService.Upgrade(); await InstallationService.Upgrade();
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Executing Upgrade {Error}", ex.Message); await logger.LogError(ex, "Error Executing Upgrade {Error}", ex.Message);
AddModuleMessage(Localizer["Error Executing Upgrade"], MessageType.Error); AddModuleMessage(Localizer["Error.Upgrade.Execute"], MessageType.Error);
} }
} }
@ -91,12 +87,12 @@
{ {
await PackageService.DownloadPackageAsync(packageid, version, "Packages"); await PackageService.DownloadPackageAsync(packageid, version, "Packages");
await PackageService.DownloadPackageAsync(Constants.UpdaterPackageId, version, "Packages"); await PackageService.DownloadPackageAsync(Constants.UpdaterPackageId, version, "Packages");
AddModuleMessage(Localizer["Framework Downloaded Successfully... Please Select Upgrade To Complete the Process"], MessageType.Success); AddModuleMessage(Localizer["Success.Framework.Download"], MessageType.Success);
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Downloading Framework Package {Error}", ex.Message); await logger.LogError(ex, "Error Downloading Framework Package {Error}", ex.Message);
AddModuleMessage(Localizer["Error Downloading Framework Package"], MessageType.Error); AddModuleMessage(Localizer["Error.Framework.Download"], MessageType.Error);
} }
} }
} }

View File

@ -4,37 +4,33 @@
@inject IUserService UserService @inject IUserService UserService
@inject INotificationService NotificationService @inject INotificationService NotificationService
@inject IStringLocalizer<Add> Localizer @inject IStringLocalizer<Add> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (PageState.User != null) @if (PageState.User != null)
{ {
<table class="table table-borderless"> <div class="container">
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="to" HelpText="Enter the username you wish to send a message to" ResourceKey="To">To: </Label>
<Label For="to" HelpText="Enter the username you wish to send a message to" ResourceKey="To">To: </Label> <div class="col-sm-9">
</td>
<td>
<input id="to" class="form-control" @bind="@username" /> <input id="to" class="form-control" @bind="@username" />
</td> </div> >
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="subject" HelpText="Enter the subject of the message" ResourceKey="Subject">Subject: </Label>
<Label For="subject" HelpText="Enter the subject of the message" ResourceKey="Subject">Subject: </Label> <div class="col-sm-9">
</td>
<td>
<input id="subject" class="form-control" @bind="@subject" /> <input id="subject" class="form-control" @bind="@subject" />
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="message" HelpText="Enter the message" ResourceKey="Message">Message: </Label>
<Label For="message" HelpText="Enter the message" ResourceKey="Message">Message: </Label> <div class="col-sm-9">
</td>
<td>
<textarea id="message" class="form-control" @bind="@body" rows="5" /> <textarea id="message" class="form-control" @bind="@body" rows="5" />
</td> </div>
</tr> </div>
</table> </div>
<button type="button" class="btn btn-primary" @onclick="Send">@Localizer["Send"]</button> <br/>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@Localizer["Cancel"]</NavLink> <button type="button" class="btn btn-primary" @onclick="Send">@SharedLocalizer["Send"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
} }
@code { @code {
@ -60,13 +56,13 @@
} }
else else
{ {
AddModuleMessage(Localizer["User Does Not Exist. Please Verify That The Username Provided Is Correct."], MessageType.Warning); AddModuleMessage(Localizer["Message.User.Invalid"], MessageType.Warning);
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Adding Notification {Error}", ex.Message); await logger.LogError(ex, "Error Adding Notification {Error}", ex.Message);
AddModuleMessage(Localizer["Error Adding Notification"], MessageType.Error); AddModuleMessage(Localizer["Error.Notification.Add"], MessageType.Error);
} }
} }

View File

@ -6,11 +6,13 @@
@inject ISettingService SettingService @inject ISettingService SettingService
@inject INotificationService NotificationService @inject INotificationService NotificationService
@inject IFileService FileService @inject IFileService FileService
@inject IFolderService FolderService
@inject IStringLocalizer<Index> Localizer @inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (PageState.User != null && photo != null) @if (PageState.User != null && photo != null)
{ {
<img src="@photo.Url" 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 else
{ {
@ -18,66 +20,56 @@ else
} }
<TabStrip> <TabStrip>
<TabPanel Name="Identity" ResourceKey="Identity"> <TabPanel Name="Identity" ResourceKey="Identity">
@if (PageState.User != null) @if (profiles != null && settings != null)
{ {
<table class="table table-borderless"> <div class="container">
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="username" HelpText="Your username. Note that this field can not be modified." ResourceKey="Username"></Label>
<label for="Name" class="control-label">@Localizer["Username:"] </label> <div class="col-sm-9">
</td> <input id="username" class="form-control" @bind="@username" readonly />
<td> </div>
<input class="form-control" @bind="@username" readonly /> </div>
</td> <div class="row mb-1 align-items-center">
</tr> <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>
<tr> <div class="col-sm-9">
<td> <input id="password" type="password" class="form-control" @bind="@password" autocomplete="new-password" />
<label for="Name" class="control-label">@Localizer["Password:"] </label> </div>
</td> </div>
<td> <div class="row mb-1 align-items-center">
<input type="password" class="form-control" @bind="@password" autocomplete="new-password" /> <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>
</td> <div class="col-sm-9">
</tr> <input id="confirm" type="password" class="form-control" @bind="@confirm" autocomplete="new-password" />
<tr> </div>
<td> </div>
<label for="Name" class="control-label">@Localizer["Confirm Password:"] </label> <div class="row mb-1 align-items-center">
</td> <Label Class="col-sm-3" For="email" HelpText="Your email address where you wish to receive notifications" ResourceKey="Email"></Label>
<td> <div class="col-sm-9">
<input type="password" class="form-control" @bind="@confirm" autocomplete="new-password" /> <input id="email" class="form-control" @bind="@email" />
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="displayname" HelpText="Your full name" ResourceKey="DisplayName"></Label>
<label for="Name" class="control-label">@Localizer["Email:"] </label> <div class="col-sm-9">
</td> <input id="displayname" class="form-control" @bind="@displayname" />
<td> </div>
<input class="form-control" @bind="@email" /> </div>
</td> <div class="row mb-1 align-items-center">
</tr> <Label Class="col-sm-3" For="@photofileid.ToString()" HelpText="A photo of yourself" ResourceKey="Photo"></Label>
<tr> <div class="col-sm-9">
<td> <FileManager FileId="@photofileid" Filter="@Constants.ImageFiles" ShowFolders="false" ShowFiles="true" UploadMultiple="false" FolderId="@folderid" @ref="filemanager" />
<label for="Name" class="control-label">@Localizer["Full Name:"] </label> </div>
</td> </div>
<td> </div>
<input class="form-control" @bind="@displayname" /> <br />
</td> <button type="button" class="btn btn-success" @onclick="Save">@SharedLocalizer["Save"]</button>
</tr> <button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
<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>
} }
</TabPanel> </TabPanel>
<TabPanel Name="Profile" ResourceKey="Profile"> <TabPanel Name="Profile" ResourceKey="Profile">
@if (profiles != null && settings != null) @if (profiles != null && settings != null)
{ {
<table class="table table-borderless"> <div class="container">
<div class="row mb-1 align-items-center">
@foreach (Profile profile in profiles) @foreach (Profile profile in profiles)
{ {
var p = profile; var p = profile;
@ -85,21 +77,17 @@ else
{ {
if (p.Category != category) if (p.Category != category)
{ {
<tr> <div class="col text-center pb-2">
<th colspan="2" style="text-align: center;">
@p.Category @p.Category
</th> </div>
</tr>
category = p.Category; category = p.Category;
} }
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="@p.Name" HelpText="@p.Description">@p.Title</Label>
<Label For="@p.Name" HelpText="@p.Description">@p.Title</Label> <div class="col-sm-9">
</td>
<td>
@if (!string.IsNullOrEmpty(p.Options)) @if (!string.IsNullOrEmpty(p.Options))
{ {
<select id="@p.Name" class="form-control" @onchange="@(e => ProfileChanged(e, p.Name))"> <select id="@p.Name" class="form-select" @onchange="@(e => ProfileChanged(e, p.Name))">
@foreach (var option in p.Options.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) @foreach (var option in p.Options.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{ {
@if (GetProfileValue(p.Name, "") == option || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == option)) @if (GetProfileValue(p.Name, "") == option || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == option))
@ -124,13 +112,14 @@ else
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" /> <input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" />
} }
} }
</td> </div>
</tr> </div>
} }
} }
</table> </div>
<button type="button" class="btn btn-primary" @onclick="Save">@Localizer["Save"]</button> </div>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@Localizer["Cancel"]</button> <button type="button" class="btn btn-success" @onclick="Save">@SharedLocalizer["Save"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
} }
</TabPanel> </TabPanel>
<TabPanel Name="Notifications" ResourceKey="Notifications"> <TabPanel Name="Notifications" ResourceKey="Notifications">
@ -205,9 +194,9 @@ else
</Pager> </Pager>
} }
<br /><hr /> <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="to">@Localizer["Inbox"]</option>
<option value="from">@Localizer["Sent Items"]</option> <option value="from">@Localizer["Items.Sent"]</option>
</select> </select>
} }
</TabPanel> </TabPanel>
@ -220,6 +209,7 @@ else
private string email = string.Empty; private string email = string.Empty;
private string displayname = string.Empty; private string displayname = string.Empty;
private FileManager filemanager; private FileManager filemanager;
private int folderid = -1;
private int photofileid = -1; private int photofileid = -1;
private File photo = null; private File photo = null;
private List<Profile> profiles; private List<Profile> profiles;
@ -240,6 +230,13 @@ else
email = PageState.User.Email; email = PageState.User.Email;
displayname = PageState.User.DisplayName; 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) if (PageState.User.PhotoFileId != null)
{ {
photofileid = PageState.User.PhotoFileId.Value; photofileid = PageState.User.PhotoFileId.Value;
@ -258,13 +255,13 @@ else
} }
else else
{ {
AddModuleMessage(Localizer["Current User Is Not Logged In"], MessageType.Warning); AddModuleMessage(Localizer["Message.User.NoLogIn"], MessageType.Warning);
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Loading User Profile {Error}", ex.Message); await logger.LogError(ex, "Error Loading User Profile {Error}", ex.Message);
AddModuleMessage(Localizer["Error Loading User Profile"], MessageType.Error); AddModuleMessage(Localizer["Error.Profile.Load"], MessageType.Error);
} }
} }
@ -304,18 +301,18 @@ else
} }
else else
{ {
AddModuleMessage(Localizer["Passwords Entered Do Not Match"], MessageType.Warning); AddModuleMessage(Localizer["Message.Password.Invalid"], MessageType.Warning);
} }
} }
else 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) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Saving User Profile {Error}", ex.Message); 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

@ -4,83 +4,80 @@
@inject IUserService UserService @inject IUserService UserService
@inject INotificationService NotificationService @inject INotificationService NotificationService
@inject IStringLocalizer<View> Localizer @inject IStringLocalizer<View> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (PageState.User != null) @if (PageState.User != null)
{ {
<table class="table table-borderless"> <div class="container">
<tr> <div class="row mb-1 align-items-center">
<td> <label Class="col-sm-3">@Localizer["Title"] </label>
<label class="control-label">@Localizer["Title:"] </label>
</td>
@if (title == "From") @if (title == "From")
{ {
<td> <div class="col-sm-3">
<input class="form-control" @bind="@username" readonly /> <input class="form-control" @bind="@username" readonly />
</td> </div>
} }
@if (title == "To") @if (title == "To")
{ {
<td> <div class="col-sm-3">
<input class="form-control" @bind="@username" /> <input class="form-control" @bind="@username" />
</td> </div>
} }
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <label Class="col-sm-3">@Localizer["Subject"] </label>
<label class="control-label">@Localizer["Subject:"] </label>
</td>
@if (title == "From") @if (title == "From")
{ {
<td> <div class="col-sm-3">
<input class="form-control" @bind="@subject" readonly /> <input class="form-control" @bind="@subject" readonly />
</td> </div>
} }
@if (title == "To") @if (title == "To")
{ {
<td> <div class="col-sm-3">
<input class="form-control" @bind="@subject" /> <input class="form-control" @bind="@subject" />
</td> </div>
} }
</tr> </div>
</div>
<div class="container">
@if (title == "From") @if (title == "From")
{ {
<tr> <div class="row mb-1 align-items-center">
<td> <label class="col-sm-3">@Localizer["Date"] </label>
<label class="control-label">@Localizer["Date:"] </label> <div class="col-sm-9">
</td>
<td>
<input class="form-control" @bind="@createdon" readonly /> <input class="form-control" @bind="@createdon" readonly />
</td> </div>
</tr> </div>
} }
@if (title == "From") @if (title == "From")
{ {
<tr> <div class="row mb-1 align-items-center">
<td> <label class="col-sm-3">@Localizer["Message"] </label>
<label class="control-label">@Localizer["Message:"] </label> <div class="col-sm-9">
</td>
<td>
<textarea class="form-control" @bind="@body" rows="5" readonly /> <textarea class="form-control" @bind="@body" rows="5" readonly />
</td> </div>
</tr> </div>
} }
@if (title == "To") @if (title == "To")
{ {
<tr>
<td> <div class="row mb-1 align-items-center">
<label class="control-label">@Localizer["Message:"] </label> <label class="col-sm-3">@Localizer["Message"] </label>
</td> <div class="col-sm-9">
<td> <textarea class="form-control" @bind="@body" rows="5" readonly />
<textarea class="form-control" @bind="@body" rows="5" /> </div>
</td> </div>
</tr>
} }
</table>
</div>
@if (reply != string.Empty) @if (reply != string.Empty)
{ {
<button type="button" class="btn btn-primary" @onclick="Send">@Localizer["Send"]</button> <button type="button" class="btn btn-primary" @onclick="Send">@SharedLocalizer["Send"]</button>
} }
else else
{ {
@ -89,7 +86,7 @@
<button type="button" class="btn btn-primary" @onclick="Reply">@Localizer["Reply"]</button> <button type="button" class="btn btn-primary" @onclick="Reply">@Localizer["Reply"]</button>
} }
} }
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@Localizer["Cancel"]</NavLink> <NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
<br /> <br />
<br /> <br />
@if (title == "To") @if (title == "To")
@ -158,7 +155,7 @@
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Loading Users {Error}", ex.Message); await logger.LogError(ex, "Error Loading Users {Error}", ex.Message);
AddModuleMessage(Localizer["Error Loading Users"], MessageType.Error); AddModuleMessage(Localizer["Error.User.Load"], MessageType.Error);
} }
} }
@ -188,13 +185,13 @@
} }
else else
{ {
AddModuleMessage(Localizer["User Does Not Exist. Please Verify That The Username Provided Is Correct."], MessageType.Warning); AddModuleMessage(Localizer["Message.User.Invalid"], MessageType.Warning);
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Adding Notification {Error}", ex.Message); await logger.LogError(ex, "Error Adding Notification {Error}", ex.Message);
AddModuleMessage(Localizer["Error Adding Notification"], MessageType.Error); AddModuleMessage(Localizer["Error.Notification.Add"], MessageType.Error);
} }
} }
} }

View File

@ -5,76 +5,65 @@
@inject IProfileService ProfileService @inject IProfileService ProfileService
@inject ISettingService SettingService @inject ISettingService SettingService
@inject IStringLocalizer<Add> Localizer @inject IStringLocalizer<Add> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<TabStrip> <TabStrip>
<TabPanel Name="Identity" ResourceKey="Identity"> <TabPanel Name="Identity" ResourceKey="Identity">
@if (profiles != null) @if (profiles != null)
{ {
<table class="table table-borderless"> <div class="container">
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="username" HelpText="A unique username for a user. Note that this field can not be modified once it is saved." ResourceKey="Username"></Label>
<label class="control-label">@Localizer["Username:"] </label> <div class="col-sm-9">
</td> <input id="username" class="form-control" @bind="@username" />
<td> </div>
<input class="form-control" @bind="@username" /> </div>
</td> <div class="row mb-1 align-items-center">
</tr> <Label Class="col-sm-3" For="password" HelpText="The user's password. Please choose a password which is sufficiently secure." ResourceKey="Password"></Label>
<tr> <div class="col-sm-9">
<td> <input id="password" type="password" class="form-control" @bind="@password" />
<label class="control-label">@Localizer["Password:"] </label> </div>
</td> </div>
<td> <div class="row mb-1 align-items-center">
<input type="password" class="form-control" @bind="@password" /> <Label Class="col-sm-3" For="confirm" HelpText="Please enter the password again to confirm it matches with the value above" ResourceKey="Confirm"></Label>
</td> <div class="col-sm-9">
</tr> <input id="confirm" type="password" class="form-control" @bind="@confirm" />
<tr> </div>
<td> </div>
<label class="control-label">@Localizer["Confirm Password:"] </label> <div class="row mb-1 align-items-center">
</td> <Label Class="col-sm-3" For="email" HelpText="The email address where the user will receive notifications" ResourceKey="Email"></Label>
<td> <div class="col-sm-9">
<input type="password" class="form-control" @bind="@confirm" /> <input id="email" class="form-control" @bind="@email" />
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="displayname" HelpText="The full name of the user" ResourceKey="DisplayName"></Label>
<label class="control-label">@Localizer["Email:"] </label> <div class="col-sm-9">
</td> <input id="displayname" class="form-control" @bind="@displayname" />
<td> </div>
<input class="form-control" @bind="@email" /> </div>
</td> </div>
</tr>
<tr>
<td>
<label class="control-label">@Localizer["Full Name:"] </label>
</td>
<td>
<input class="form-control" @bind="@displayname" />
</td>
</tr>
</table>
} }
</TabPanel> </TabPanel>
<TabPanel Name="Profile" ResourceKey="Profile"> <TabPanel Name="Profile" ResourceKey="Profile">
@if (profiles != null) @if (profiles != null)
{ {
<table class="table table-borderless"> <div class="container">
<div class="row mb-1 align-items-center">
@foreach (Profile profile in profiles) @foreach (Profile profile in profiles)
{ {
var p = profile; var p = profile;
if (p.Category != category) if (p.Category != category)
{ {
<tr> <div class="col text-center pb-2">
<th colspan="2" style="text-align: center;"> <strong>@p.Category</strong>
@p.Category </div>
</th>
</tr>
category = p.Category; category = p.Category;
} }
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="@p.Name" HelpText="@p.Description">@p.Title</Label>
<Label For="@p.Name" HelpText="@p.Description">@p.Title</Label> <div class="col-sm-9">
</td>
<td>
@if (p.IsRequired) @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))" /> <input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))" />
@ -83,16 +72,19 @@
{ {
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" /> <input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" />
} }
</td> </div>
</tr> </div>
} }
</table>
</div>
</div>
} }
</TabPanel> </TabPanel>
</TabStrip> </TabStrip>
<br />
<button type="button" class="btn btn-primary" @onclick="SaveUser">@Localizer["Save"]</button> <br />
<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>
@code { @code {
private string username = string.Empty; private string username = string.Empty;
@ -116,7 +108,7 @@
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Loading User Profile {Error}", ex.Message); await logger.LogError(ex, "Error Loading User Profile {Error}", ex.Message);
AddModuleMessage(Localizer["Error Loading User Profile"], MessageType.Error); AddModuleMessage(Localizer["Error.Profile.Load"], MessageType.Error);
} }
} }
@ -153,28 +145,28 @@
else else
{ {
await logger.LogError("Error Adding User {Username} {Email}", username, email); await logger.LogError("Error Adding User {Username} {Email}", username, email);
AddModuleMessage(Localizer["Error Adding User. Please Ensure Password Meets Complexity Requirements And Username And Email Are Not Already In Use."], MessageType.Error); AddModuleMessage(Localizer["Error.User.AddCheckPass"], MessageType.Error);
} }
} }
else else
{ {
AddModuleMessage(Localizer["Username Already Exists"], MessageType.Warning); AddModuleMessage(Localizer["Message.Username.Exists"], MessageType.Warning);
} }
} }
else else
{ {
AddModuleMessage(Localizer["Passwords Entered Do Not Match"], MessageType.Warning); AddModuleMessage(Localizer["Message.Password.NoMatch"], MessageType.Warning);
} }
} }
else 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) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Adding User {Username} {Email} {Error}", username, email, ex.Message); await logger.LogError(ex, "Error Adding User {Username} {Email} {Error}", username, email, ex.Message);
AddModuleMessage(Localizer["Error Adding User"], MessageType.Error); AddModuleMessage(Localizer["Error.User.Add"], MessageType.Error);
} }
} }

View File

@ -6,6 +6,7 @@
@inject ISettingService SettingService @inject ISettingService SettingService
@inject IFileService FileService @inject IFileService FileService
@inject IStringLocalizer<Edit> Localizer @inject IStringLocalizer<Edit> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (PageState.User != null && photo != null) @if (PageState.User != null && photo != null)
{ {
@ -19,90 +20,92 @@ else
<TabPanel Name="Identity" ResourceKey="Identity"> <TabPanel Name="Identity" ResourceKey="Identity">
@if (profiles != null) @if (profiles != null)
{ {
<table class="table table-borderless"> <div class="container">
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="username" HelpText="The unique username for a user. Note that this field can not be modified." ResourceKey="Username"></Label>
<label class="control-label">@Localizer["Username:"] </label> <div class="col-sm-9">
</td> <input id="username" class="form-control" @bind="@username" readonly />
<td> </div>
<input class="form-control" @bind="@username" readonly /> </div>
</td> <div class="row mb-1 align-items-center">
</tr> <Label Class="col-sm-3" For="password" HelpText="The user's password. Please choose a password which is sufficiently secure." ResourceKey="Password"></Label>
<tr> <div class="col-sm-9">
<td> <input id="password" type="password" class="form-control" @bind="@password" />
<label class="control-label">@Localizer["Password:"] </label> </div>
</td> </div>
<td> <div class="row mb-1 align-items-center">
<input type="password" class="form-control" @bind="@password" /> <Label Class="col-sm-3" For="confirm" HelpText="Please enter the password again to confirm it matches with the value above" ResourceKey="Confirm"></Label>
</td> <div class="col-sm-9">
</tr> <input id="confirm" type="password" class="form-control" @bind="@confirm" />
<tr> </div>
<td> </div>
<label class="control-label">@Localizer["Confirm Password:"] </label> <div class="row mb-1 align-items-center">
</td> <Label Class="col-sm-3" For="email" HelpText="The email address where the user will receive notifications" ResourceKey="Email"></Label>
<td> <div class="col-sm-9">
<input type="password" class="form-control" @bind="@confirm" /> <input id="email" class="form-control" @bind="@email" />
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="displayname" HelpText="The full name of the user" ResourceKey="DisplayName"></Label>
<label class="control-label">@Localizer["Email:"] </label> <div class="col-sm-9">
</td> <input id="displayname" class="form-control" @bind="@displayname" />
<td> </div>
<input class="form-control" @bind="@email" /> </div>
</td> <div class="row mb-1 align-items-center">
</tr> <Label Class="col-sm-3" For="@photofileid.ToString()" HelpText="A photo of the user" ResourceKey="Photo"></Label>
<tr> <div class="col-sm-9">
<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>
<FileManager FileId="@photofileid" @ref="filemanager" /> <FileManager FileId="@photofileid" @ref="filemanager" />
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="isdeleted" HelpText="Indicate if the user is active" ResourceKey="IsDeleted"></Label>
<label class="control-label">@Localizer["Is Deleted?"] </label> <div class="col-sm-9">
</td> <select id="isdeleted" class="form-select" @bind="@isdeleted">
<td> <option value="True">@SharedLocalizer["Yes"]</option>
<select class="form-control" @bind="@isdeleted"> <option value="False">@SharedLocalizer["No"]</option>
<option value="True">@Localizer["Yes"]</option>
<option value="False">@Localizer["No"]</option>
</select> </select>
</td> </div>
</tr> </div>
</table> </div>
} }
</TabPanel> </TabPanel>
<TabPanel Name="Profile" ResourceKey="Profile"> <TabPanel Name="Profile" ResourceKey="Profile">
@if (profiles != null) @if (profiles != null)
{ {
<table class="table table-borderless"> <div class="container">
<div class="row mb-1 align-items-center">
@foreach (Profile profile in profiles) @foreach (Profile profile in profiles)
{ {
var p = profile; var p = profile;
if (p.Category != category) if (p.Category != category)
{ {
<tr> <div class="col text-center pb-2">
<th colspan="2" style="text-align: center;"> <strong>@p.Category</strong>
@p.Category </div>
</th>
</tr>
category = p.Category; category = p.Category;
} }
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="@p.Name" HelpText="@p.Description">@p.Title</Label>
<Label For="@p.Name" HelpText="@p.Description">@p.Title</Label> <div class="col-sm-9">
</td> @if (!string.IsNullOrEmpty(p.Options))
<td> {
<select id="@p.Name" class="form-select" @onchange="@(e => ProfileChanged(e, p.Name))">
@foreach (var option in p.Options.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
@if (GetProfileValue(p.Name, "") == option || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == option))
{
<option value="@option" selected>@option</option>
}
else
{
<option value="@option">@option</option>
}
}
</select>
}
else
{
@if (p.IsRequired) @if (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))" /> <input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))" />
@ -111,16 +114,18 @@ else
{ {
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" /> <input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" />
} }
</td>
</tr>
} }
</table> </div>
</div>
}
</div>
</div>
} }
</TabPanel> </TabPanel>
</TabStrip> </TabStrip>
<button type="button" class="btn btn-primary" @onclick="SaveUser">@Localizer["Save"]</button> <button type="button" class="btn btn-success" @onclick="SaveUser">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@Localizer["Cancel"]</NavLink> <NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
<br /> <br />
<br /> <br />
<AuditInfo CreatedBy="@createdby" CreatedOn="@createdon" ModifiedBy="@modifiedby" ModifiedOn="@modifiedon" DeletedBy="@deletedby" DeletedOn="@deletedon"></AuditInfo> <AuditInfo CreatedBy="@createdby" CreatedOn="@createdon" ModifiedBy="@modifiedby" ModifiedOn="@modifiedon" DeletedBy="@deletedby" DeletedOn="@deletedon"></AuditInfo>
@ -151,9 +156,11 @@ else
protected override async Task OnParametersSetAsync() protected override async Task OnParametersSetAsync()
{ {
try try
{
// OnParametersSetAsync is called when the edit modal is closed - in which case there is no id parameter
if (PageState.QueryString.ContainsKey("id"))
{ {
profiles = await ProfileService.GetProfilesAsync(PageState.Site.SiteId); profiles = await ProfileService.GetProfilesAsync(PageState.Site.SiteId);
userid = Int32.Parse(PageState.QueryString["id"]); userid = Int32.Parse(PageState.QueryString["id"]);
var user = await UserService.GetUserAsync(userid, PageState.Site.SiteId); var user = await UserService.GetUserAsync(userid, PageState.Site.SiteId);
if (user != null) if (user != null)
@ -181,10 +188,11 @@ else
isdeleted = user.IsDeleted.ToString(); isdeleted = user.IsDeleted.ToString();
} }
} }
}
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Loading User {UserId} {Error}", userid, ex.Message); await logger.LogError(ex, "Error Loading User {UserId} {Error}", userid, ex.Message);
AddModuleMessage(Localizer["Error Loading User"], MessageType.Error); AddModuleMessage(Localizer["Error.User.Load"], MessageType.Error);
} }
} }
@ -222,18 +230,18 @@ else
} }
else else
{ {
AddModuleMessage(Localizer["Passwords Entered Do Not Match"], MessageType.Warning); AddModuleMessage(Localizer["Message.Password.NoMatch"], MessageType.Warning);
} }
} }
else 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) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Saving User {Username} {Email} {Error}", username, email, ex.Message); 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,39 +4,42 @@
@inject IUserService UserService @inject IUserService UserService
@inject ISettingService SettingService @inject ISettingService SettingService
@inject IStringLocalizer<Index> Localizer @inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (userroles == null) @if (userroles == null)
{ {
<p> <p>
<em>@Localizer["Loading..."]</em> <em>@SharedLocalizer["Loading"]</em>
</p> </p>
} }
else else
{ {
<div class="form-row"> <div class="container">
<div class="col"> <div class="row mb-1 align-items-center">
<div class="col-sm-4">
<ActionLink Action="Add" Text="Add User" ResourceKey="AddUser" /> <ActionLink Action="Add" Text="Add User" ResourceKey="AddUser" />
</div> </div>
<div class="col"> <div class="col-sm-4">
<div class="input-group flex-nowrap"> <input class="form-control" @bind="@_search" />
<input class="form-control" @bind="@_search" />&nbsp;<button class="btn btn-secondary" @onclick="OnSearch">@Localizer["Search"]</button> </div>
<div class="col-sm-4">
<button class="btn btn-secondary" @onclick="OnSearch">@SharedLocalizer["Search"]</button>
</div> </div>
</div> </div>
</div> </div>
<Pager Items="@userroles"> <Pager Items="@userroles">
<Header> <Header>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th>@Localizer["Name"]</th> <th>@SharedLocalizer["Name"]</th>
</Header> </Header>
<Row> <Row>
<td> <td>
<ActionLink Action="Edit" Parameters="@($"id=" + context.UserId.ToString())" ResourceKey="EditUser" /> <ActionLink Action="Edit" Parameters="@($"id=" + context.UserId.ToString())" ResourceKey="EditUser" />
</td> </td>
<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))" Disabled="@(context.Role.Name == RoleNames.Host)" 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>
<td> <td>
<ActionLink Action="Roles" Parameters="@($"id=" + context.UserId.ToString())" ResourceKey="Roles" /> <ActionLink Action="Roles" Parameters="@($"id=" + context.UserId.ToString())" ResourceKey="Roles" />
@ -92,6 +95,8 @@ else
{ {
await UserService.DeleteUserAsync(user.UserId, PageState.Site.SiteId); await UserService.DeleteUserAsync(user.UserId, PageState.Site.SiteId);
await logger.LogInformation("User Deleted {User}", UserRole.User); await logger.LogInformation("User Deleted {User}", UserRole.User);
allroles = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId);
userroles = Search(_search);
StateHasChanged(); StateHasChanged();
} }
} }

View File

@ -4,56 +4,51 @@
@inject IUserService UserService @inject IUserService UserService
@inject IUserRoleService UserRoleService @inject IUserRoleService UserRoleService
@inject IStringLocalizer<Roles> Localizer @inject IStringLocalizer<Roles> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (userroles == null) @if (userroles == null)
{ {
<p><em>@Localizer["Loading..."]</em></p> <p><em>@SharedLocalizer["Loading"]</em></p>
} }
else else
{ {
<table class="table table-borderless"> <div class="container">
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="user" HelpText="The user you are assigning roles to" ResourceKey="User">User: </Label>
<Label For="user" HelpText="The user you are assigning roles to" ResourceKey="User">User: </Label> <div class="col-sm-9">
</td>
<td>
<input id="user" class="form-control" @bind="@name" disabled /> <input id="user" class="form-control" @bind="@name" disabled />
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="role" HelpText="Select a role" ResourceKey="Role">Role: </Label>
<Label For="role" HelpText="Select a role" ResourceKey="Role">Role: </Label> <div class="col-sm-9">
</td> <select id="role" class="form-select" @bind="@roleid">
<td> <option value="-1">&lt;@Localizer["Role.Select"]&gt;</option>
<select id="role" class="form-control" @bind="@roleid">
<option value="-1">&lt;@Localizer["Select Role"]&gt;</option>
@foreach (Role role in roles) @foreach (Role role in roles)
{ {
<option value="@(role.RoleId)">@role.Name</option> <option value="@(role.RoleId)">@role.Name</option>
} }
</select> </select>
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="effectiveDate" HelpText="The date that this role assignment is active" ResourceKey="EffectiveDate">Effective Date: </Label>
<Label For="effectiveDate" HelpText="The date that this role assignment is active" ResourceKey="EffectiveDate">Effective Date: </Label> <div class="col-sm-9">
</td>
<td>
<input id="effectiveDate" class="form-control" @bind="@effectivedate" /> <input id="effectiveDate" class="form-control" @bind="@effectivedate" />
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="expiryDate" HelpText="The date that this role assignment expires" ResourceKey="ExpiryDate">Expiry Date: </Label>
<Label For="expiryDate" HelpText="The date that this role assignment expires" ResourceKey="ExpiryDate">Expiry Date: </Label> <div class="col-sm-9">
</td>
<td>
<input id="expiryDate" class="form-control" @bind="@expirydate" /> <input id="expiryDate" class="form-control" @bind="@expirydate" />
</td> </div>
</tr> </div>
</table>
<button type="button" class="btn btn-success" @onclick="SaveUserRole">@Localizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@Localizer["Cancel"]</NavLink>
</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" /> <hr class="app-rule" />
<p align="center"> <p align="center">
<Pager Items="@userroles"> <Pager Items="@userroles">
@ -68,7 +63,7 @@ else
<td>@context.EffectiveDate</td> <td>@context.EffectiveDate</td>
<td>@context.ExpiryDate</td> <td>@context.ExpiryDate</td>
<td> <td>
<ActionDialog Header="Remove Role" Message="@Localizer["Are You Sure You Wish To Remove This User From The {0} Role?", context.Role.Name]" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteUserRole(context.UserRoleId))" Disabled="@(context.Role.IsAutoAssigned || (context.Role.Name == RoleNames.Host && userid == PageState.User.UserId))" ResourceKey="DeleteUserRole" /> <ActionDialog Header="Remove Role" Message="@string.Format(Localizer["Confirm.User.RemoveRole"], context.Role.Name)" Action="Delete" Security="SecurityAccessLevel.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> </td>
</Row> </Row>
</Pager> </Pager>
@ -107,7 +102,7 @@ else
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Loading Roles {Error}", ex.Message); await logger.LogError(ex, "Error Loading Roles {Error}", ex.Message);
AddModuleMessage(Localizer["Error Loading Roles"], MessageType.Error); AddModuleMessage(Localizer["Error.LoadRole"], MessageType.Error);
} }
} }
@ -121,7 +116,7 @@ else
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Loading User Roles {UserId} {Error}", userid, ex.Message); 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);
} }
} }
@ -181,19 +176,19 @@ else
} }
await logger.LogInformation("User Assigned To Role {UserRole}", userrole); 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(); await GetUserRoles();
StateHasChanged(); StateHasChanged();
} }
else else
{ {
AddModuleMessage(Localizer["You Must Select A Role"], MessageType.Warning); AddModuleMessage(Localizer["Message.Required.Role"], MessageType.Warning);
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Saving User Roles {UserId} {Error}", userid, ex.Message); 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);
} }
} }
@ -203,14 +198,14 @@ else
{ {
await UserRoleService.DeleteUserRoleAsync(UserRoleId); await UserRoleService.DeleteUserRoleAsync(UserRoleId);
await logger.LogInformation("User Removed From Role {UserRoleId}", UserRoleId); 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(); await GetUserRoles();
StateHasChanged(); StateHasChanged();
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Removing User From Role {UserRoleId} {Error}", UserRoleId, ex.Message); 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-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title">@Header</h5> <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>
<div class="modal-body"> <div class="modal-body">
<p>@Message</p> <p>@Message</p>

View File

@ -44,12 +44,12 @@
if (!String.IsNullOrEmpty(CreatedBy)) if (!String.IsNullOrEmpty(CreatedBy))
{ {
_text += $" {Localizer["by"]} <b>{CreatedBy}</b>"; _text += $" {Localizer["By"]} <b>{CreatedBy}</b>";
} }
if (CreatedOn != null) if (CreatedOn != null)
{ {
_text += $" {Localizer["on"]} <b>{CreatedOn.Value.ToString("MMM dd yyyy HH:mm:ss")}</b>"; _text += $" {Localizer["On"]} <b>{CreatedOn.Value.ToString("MMM dd yyyy HH:mm:ss")}</b>";
} }
_text += "</p>"; _text += "</p>";
@ -57,7 +57,7 @@
if (!String.IsNullOrEmpty(ModifiedBy) || ModifiedOn.HasValue) if (!String.IsNullOrEmpty(ModifiedBy) || ModifiedOn.HasValue)
{ {
_text += $"<p style=\"{Style}\">{Localizer["Last modified"]} "; _text += $"<p style=\"{Style}\">{Localizer["LastModified"]} ";
if (!String.IsNullOrEmpty(ModifiedBy)) if (!String.IsNullOrEmpty(ModifiedBy))
{ {
@ -78,12 +78,12 @@
if (!String.IsNullOrEmpty(DeletedBy)) if (!String.IsNullOrEmpty(DeletedBy))
{ {
_text += $" {Localizer["by"]} <b>{DeletedBy}</b>"; _text += $" {Localizer["By"]} <b>{DeletedBy}</b>";
} }
if (DeletedOn != null) if (DeletedOn != null)
{ {
_text += $" {Localizer["on"]} <b>{DeletedOn.Value.ToString("MMM dd yyyy HH:mm:ss")}</b>"; _text += $" {Localizer["On"]} <b>{DeletedOn.Value.ToString("MMM dd yyyy HH:mm:ss")}</b>";
} }
_text += "</p>"; _text += "</p>";

View File

@ -3,6 +3,7 @@
@inject IFolderService FolderService @inject IFolderService FolderService
@inject IFileService FileService @inject IFileService FileService
@inject IStringLocalizer<FileManager> Localizer @inject IStringLocalizer<FileManager> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_folders != null) @if (_folders != null)
{ {
@ -12,10 +13,10 @@
@if (ShowFolders || FolderId <= 0) @if (ShowFolders || FolderId <= 0)
{ {
<div> <div>
<select class="form-control" value="@FolderId" @onchange="(e => FolderChanged(e))"> <select class="form-select" value="@FolderId" @onchange="(e => FolderChanged(e))">
@if (string.IsNullOrEmpty(Folder)) @if (string.IsNullOrEmpty(Folder))
{ {
<option value="-1">&lt;@Localizer["Select Folder"]&gt;</option> <option value="-1">&lt;@Localizer["Folder.Select"]&gt;</option>
} }
@foreach (Folder folder in _folders) @foreach (Folder folder in _folders)
{ {
@ -27,8 +28,8 @@
@if (ShowFiles) @if (ShowFiles)
{ {
<div> <div>
<select class="form-control" value="@FileId" @onchange="(e => FileChanged(e))"> <select class="form-select" value="@FileId" @onchange="(e => FileChanged(e))">
<option value="-1">&lt;@Localizer["Select File"]&gt;</option> <option value="-1">&lt;@Localizer["File.Select"]&gt;</option>
@foreach (File file in _files) @foreach (File file in _files)
{ {
<option value="@(file.FileId)">@(file.Name)</option> <option value="@(file.FileId)">@(file.Name)</option>
@ -48,16 +49,15 @@
<input type="file" id="@_fileinputid" name="file" accept="@_filter" /> <input type="file" id="@_fileinputid" name="file" accept="@_filter" />
} }
<span id="@_progressinfoid"></span><progress id="@_progressbarid" style="width: 150px; visibility: hidden;"></progress> <span id="@_progressinfoid"></span><progress id="@_progressbarid" style="width: 150px; visibility: hidden;"></progress>
<span class="float-right"> <span class="float-end">
<button type="button" class="btn btn-success" @onclick="UploadFile">@Localizer["Upload"]</button> <button type="button" class="btn btn-success" @onclick="UploadFile">@SharedLocalizer["Upload"]</button>
@if (ShowFiles && GetFileId() != -1) @if (ShowFiles && GetFileId() != -1)
{ {
<button type="button" class="btn btn-danger" @onclick="DeleteFile">@Localizer["Delete"]</button> <button type="button" class="btn btn-danger" @onclick="DeleteFile">@SharedLocalizer["Delete"]</button>
} }
</span> </span>
</div> </div>
} }
<ModuleMessage Message="@_message" Type="@_messagetype"></ModuleMessage>
</div> </div>
@if (_image != string.Empty) @if (_image != string.Empty)
{ {
@ -66,6 +66,14 @@
</div> </div>
} }
</div> </div>
@if (!string.IsNullOrEmpty(_message))
{
<div class="row">
<div class="col mt-2">
<ModuleMessage Message="@_message" Type="@_messagetype" />
</div>
</div>
}
</div> </div>
} }
@ -88,7 +96,7 @@
public string Id { get; set; } // optional - for setting the id of the FileManager component for accessibility public string Id { get; set; } // optional - for setting the id of the FileManager component for accessibility
[Parameter] [Parameter]
public string Folder { get; set; } // optional - for setting a specific folder by default public string Folder { get; set; } // optional - for setting a specific folder by default ( only relevant for host functions )
[Parameter] [Parameter]
public int FolderId { get; set; } = -1; // optional - for setting a specific folderid by default public int FolderId { get; set; } = -1; // optional - for setting a specific folderid by default
@ -103,7 +111,10 @@
public bool ShowFolders { get; set; } = true; // optional - for indicating whether a list of folders should be displayed - default is true public bool ShowFolders { get; set; } = true; // optional - for indicating whether a list of folders should be displayed - default is true
[Parameter] [Parameter]
public 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 int FileId { get; set; } = -1; // optional - for selecting a specific file by default
[Parameter] [Parameter]
public string Filter { get; set; } // optional - comma delimited list of file types that can be selected or uploaded ie. "jpg,gif" public string Filter { get; set; } // optional - comma delimited list of file types that can be selected or uploaded ie. "jpg,gif"
@ -111,6 +122,15 @@
[Parameter] [Parameter]
public bool UploadMultiple { get; set; } = false; // optional - enable multiple file uploads - default false public bool UploadMultiple { get; set; } = false; // optional - enable multiple file uploads - default false
[Parameter]
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() protected override async Task OnInitializedAsync()
{ {
if (!string.IsNullOrEmpty(Id)) if (!string.IsNullOrEmpty(Id))
@ -118,6 +138,11 @@
_id = Id; _id = Id;
} }
if (!ShowFiles)
{
ShowImage = false;
}
if (!string.IsNullOrEmpty(Folder)) if (!string.IsNullOrEmpty(Folder))
{ {
_folders = new List<Folder> { new Folder { FolderId = -1, Name = Folder } }; _folders = new List<Folder> { new Folder { FolderId = -1, Name = Folder } };
@ -134,12 +159,14 @@
if (file != null) if (file != null)
{ {
FolderId = file.FolderId; FolderId = file.FolderId;
await OnSelect.InvokeAsync(FileId);
} }
else else
{ {
FileId = -1; // file does not exist FileId = -1; // file does not exist
} }
} }
await SetImage(); await SetImage();
if (!string.IsNullOrEmpty(Filter)) if (!string.IsNullOrEmpty(Filter))
@ -208,7 +235,7 @@
{ {
await logger.LogError(ex, "Error Loading Files {Error}", ex.Message); await logger.LogError(ex, "Error Loading Files {Error}", ex.Message);
_message = Localizer["Error Loading Files"]; _message = Localizer["Error.File.Load"];
_messagetype = MessageType.Error; _messagetype = MessageType.Error;
} }
} }
@ -217,6 +244,10 @@
{ {
_message = string.Empty; _message = string.Empty;
FileId = int.Parse((string)e.Value); FileId = int.Parse((string)e.Value);
if (FileId != -1)
{
await OnSelect.InvokeAsync(FileId);
}
await SetImage(); await SetImage();
StateHasChanged(); StateHasChanged();
@ -229,7 +260,7 @@
if (FileId != -1) if (FileId != -1)
{ {
_file = await FileService.GetFileAsync(FileId); _file = await FileService.GetFileAsync(FileId);
if (_file != null && _file.ImageHeight != 0 && _file.ImageWidth != 0) if (_file != null && ShowImage && _file.ImageHeight != 0 && _file.ImageWidth != 0)
{ {
var maxwidth = 200; var maxwidth = 200;
var maxheight = 200; var maxheight = 200;
@ -268,19 +299,17 @@
{ {
await logger.LogInformation("File Upload Succeeded {Files}", upload); await logger.LogInformation("File Upload Succeeded {Files}", upload);
_message = Localizer["File Upload Succeeded"]; _message = Localizer["Success.File.Upload"];
_messagetype = MessageType.Success; _messagetype = MessageType.Success;
// set FileId to first file in upload collection
await GetFiles(); await GetFiles();
if (upload.Length == 1)
{
var file = _files.Where(item => item.Name == upload[0]).FirstOrDefault(); var file = _files.Where(item => item.Name == upload[0]).FirstOrDefault();
if (file != null) if (file != null)
{ {
FileId = file.FileId; FileId = file.FileId;
await SetImage(); await SetImage();
} await OnUpload.InvokeAsync(FileId);
} }
StateHasChanged(); StateHasChanged();
} }
@ -288,7 +317,7 @@
{ {
await logger.LogError("File Upload Failed For {Files}", result.Replace(",", ", ")); await logger.LogError("File Upload Failed For {Files}", result.Replace(",", ", "));
_message = Localizer["File Upload Failed"]; _message = Localizer["Error.File.Upload"];
_messagetype = MessageType.Error; _messagetype = MessageType.Error;
} }
} }
@ -296,13 +325,13 @@
{ {
await logger.LogError(ex, "File Upload Failed {Error}", ex.Message); await logger.LogError(ex, "File Upload Failed {Error}", ex.Message);
_message = Localizer["File Upload Failed"]; _message = Localizer["Error.File.Upload"];
_messagetype = MessageType.Error; _messagetype = MessageType.Error;
} }
} }
else else
{ {
_message = Localizer["You Have Not Selected A File To Upload"]; _message = Localizer["Message.File.NotSelected"];
_messagetype = MessageType.Warning; _messagetype = MessageType.Warning;
} }
} }
@ -314,8 +343,9 @@
{ {
await FileService.DeleteFileAsync(FileId); await FileService.DeleteFileAsync(FileId);
await logger.LogInformation("File Deleted {File}", FileId); await logger.LogInformation("File Deleted {File}", FileId);
await OnDelete.InvokeAsync(FileId);
_message = Localizer["File Deleted"]; _message = Localizer["Success.File.Delete"];
_messagetype = MessageType.Success; _messagetype = MessageType.Success;
await GetFiles(); await GetFiles();
@ -327,7 +357,7 @@
{ {
await logger.LogError(ex, "Error Deleting File {File} {Error}", FileId, ex.Message); 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; _messagetype = MessageType.Error;
} }
} }

View File

@ -3,17 +3,16 @@
@if (!string.IsNullOrEmpty(HelpText)) @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 else
{ {
@((MarkupString)_openLabel)@ChildContent@((MarkupString)_closeLabel) <label for="@For" class="@_labelclass">@ChildContent</label>
} }
@code { @code {
private string _openLabel = string.Empty;
private string _closeLabel = "</label>";
[Parameter] [Parameter]
public RenderFragment ChildContent { get; set; } 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 public string For { get; set; } // optional - the id of the associated input control for accessibility
[Parameter] [Parameter]
public string Class { get; set; } // optional - the class for the label ( ie. control-label ) public string Class { get; set; } // optional - CSS classes
[Parameter] [Parameter]
public string HelpText { get; set; } // optional - tooltip for this label 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() protected override void OnParametersSet()
{ {
base.OnParametersSet(); base.OnParametersSet();
_openLabel = "<label"; if (!string.IsNullOrEmpty(HelpText))
if (!string.IsNullOrEmpty(For))
{ {
_openLabel += " for=\"" + For + "\""; _helptext = Localize(nameof(HelpText), HelpText);
_spanclass += (!string.IsNullOrEmpty(Class)) ? " " + Class : "";
} }
else
if (!string.IsNullOrEmpty(Class))
{ {
_openLabel += " class=\"" + Class + "\""; _labelclass += (!string.IsNullOrEmpty(Class)) ? " " + Class : "";
} }
_openLabel += ">";
var text = Localize("Text", String.Empty); var text = Localize("Text", String.Empty);
if (text != String.Empty) if (!string.IsNullOrEmpty(text))
{ {
ChildContent =@<text>@text</text>; ChildContent =@<text>@text</text>;
} }
HelpText = Localize(nameof(HelpText), HelpText);
} }
} }

View File

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

View File

@ -2,45 +2,57 @@
@inherits ModuleControlBase @inherits ModuleControlBase
@typeparam TableItem @typeparam TableItem
<p> @if (ItemList != null)
@if (Toolbar == "Top")
{ {
<div class="mx-auto text-center"> @if (Toolbar == "Top" && _pages > 0 && Items.Count() > _maxItems)
@if (_endPage > 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> <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)
{
<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>
<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> </li>
}
@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++) @for (int i = _startPage; i <= _endPage; i++)
{ {
var pager = i; var pager = i;
<button class="btn @((pager == _page) ? "btn-primary" : "btn-link")" @onclick=@(async () => UpdateList(pager))> if (pager == _page)
@pager
</button>
}
<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)
{ {
<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 active">
<a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a>
</li>
} }
@if (_endPage > 1) else
{ {
<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> <li class="page-item">
<a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a>
</li>
} }
@if (_endPage > 1) }
<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)
{ {
<span class="btn btn-link disabled">Page @_page of @_pages</span> <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>
} }
</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>
} }
@if (Format == "Table") @if (Format == "Table" && Row != null)
{ {
<table class="@Class"> <table class="@Class">
<thead> <thead>
@ -58,90 +70,125 @@
</tbody> </tbody>
</table> </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="@Class">
<div class="row">@Header</div> @if (Header != null)
@foreach (var item in ItemList)
{ {
<div class="row">@Row(item)</div> <div class="row"><div class="col">@Header</div></div>
@if (Detail != null) }
@for (int row = 0; row < (count / _columns); row++)
{ {
<div class="row">@Detail(item)</div> <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") </div>
{
<div class="mx-auto text-center">
@if (_endPage > 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>
} }
@if (_page > _maxPages) @if (Toolbar == "Bottom" && _pages > 0 && Items.Count() > _maxItems)
{ {
<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> <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)
{
<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 (_endPage > 1) <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>
<button class="btn btn-secondary mr-1" @onclick=@(async () => NavigateToPage("previous"))><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></button> </li>
@for (int i = _startPage; i <= _endPage; i++) @for (int i = _startPage; i <= _endPage; i++)
{ {
var pager = i; var pager = i;
<button class="btn @((pager == _page) ? "btn-primary" : "btn-link")" @onclick=@(async () => UpdateList(pager))> if (pager == _page)
@pager
</button>
}
<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)
{ {
<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 active">
<a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a>
</li>
} }
@if (_endPage > 1) else
{ {
<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> <li class="page-item">
<a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a>
</li>
} }
@if (_endPage > 1) }
<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)
{ {
<span class="btn btn-link disabled">Page @_page of @_pages</span> <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>
}
<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>
} }
</div>
} }
</p>
@code { @code {
private int _pages = 0; private int _pages = 0;
private int _page = 1; private int _page = 1;
private int _maxItems = 10; private int _maxItems = 10;
private int _maxPages = 5; private int _displayPages = 5;
private int _startPage = 0; private int _startPage = 0;
private int _endPage = 0; private int _endPage = 0;
private int _columns = 1;
[Parameter] [Parameter]
public string Format { get; set; } public string Format { get; set; } // Table or Grid
[Parameter] [Parameter]
public string Toolbar { get; set; } public string Toolbar { get; set; } // Top or Bottom
[Parameter] [Parameter]
public RenderFragment Header { get; set; } public RenderFragment Header { get; set; } = null;
[Parameter] [Parameter]
public RenderFragment<TableItem> Row { get; set; } public RenderFragment<TableItem> Row { get; set; } = null;
[Parameter] [Parameter]
public RenderFragment<TableItem> Detail { get; set; } public RenderFragment<TableItem> Detail { get; set; } = null; // only applicable to Table layouts
[Parameter] [Parameter]
public IEnumerable<TableItem> Items { get; set; } public IEnumerable<TableItem> Items { get; set; } // the IEnumerable data source
[Parameter] [Parameter]
public string PageSize { get; set; } public string PageSize { get; set; } // number of items to display on a page
[Parameter] [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] [Parameter]
public string Class { get; set; } public string Class { get; set; }
@ -177,86 +224,89 @@
_maxItems = int.Parse(PageSize); _maxItems = int.Parse(PageSize);
} }
if (!string.IsNullOrEmpty(DisplayPages)) if (!string.IsNullOrEmpty(Columns))
{ {
_maxPages = int.Parse(DisplayPages); _columns = int.Parse(Columns);
} }
if (!string.IsNullOrEmpty(DisplayPages))
{
_displayPages = int.Parse(DisplayPages);
}
if (!string.IsNullOrEmpty(CurrentPage))
{
_page = int.Parse(CurrentPage);
}
else
{
_page = 1; _page = 1;
}
_startPage = 0; _startPage = 0;
_endPage = 0; _endPage = 0;
if (Items != null) if (Items != null)
{ {
ItemList = Items.Skip((_page - 1) * _maxItems).Take(_maxItems);
_pages = (int)Math.Ceiling(Items.Count() / (decimal)_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 SetPagerSize()
}
public void UpdateList(int currentPage)
{ {
ItemList = Items.Skip((currentPage - 1) * _maxItems).Take(_maxItems); _startPage = ((_page - 1) / _displayPages) * _displayPages + 1;
_page = currentPage; _endPage = _startPage + _displayPages - 1;
if (_endPage > _pages)
StateHasChanged();
}
public void SetPagerSize(string direction)
{
if (direction == "forward")
{
if (_endPage + 1 < _pages)
{
_startPage = _endPage + 1;
}
else
{
_startPage = 1;
}
if (_endPage + _maxPages < _pages)
{
_endPage = _startPage + _maxPages - 1;
}
else
{ {
_endPage = _pages; _endPage = _pages;
} }
StateHasChanged(); StateHasChanged();
} }
else if (direction == "back")
public void UpdateList(int page)
{ {
_endPage = _startPage - 1; ItemList = Items.Skip((page - 1) * _maxItems).Take(_maxItems);
_startPage = _startPage - _maxPages; _page = page;
SetPagerSize();
} }
public void SkipPages(string direction)
{
switch (direction)
{
case "forward":
_page = _endPage + 1;
break;
case "back":
_page = _startPage - 1;
break;
}
SetPagerSize();
} }
public void NavigateToPage(string direction) public void NavigateToPage(string direction)
{ {
if (direction == "next") switch (direction)
{ {
case "next":
if (_page < _pages) if (_page < _pages)
{ {
if (_page == _endPage)
{
SetPagerSize("forward");
}
_page += 1; _page += 1;
} }
} break;
else if (direction == "previous") case "previous":
{
if (_page > 1) if (_page > 1)
{ {
if (_page == _startPage)
{
SetPagerSize("back");
}
_page -= 1; _page -= 1;
} }
break;
} }
UpdateList(_page); UpdateList(_page);

View File

@ -3,11 +3,14 @@
@inject IRoleService RoleService @inject IRoleService RoleService
@inject IUserService UserService @inject IUserService UserService
@inject IStringLocalizer<PermissionGrid> Localizer @inject IStringLocalizer<PermissionGrid> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_permissions != null) @if (_permissions != null)
{ {
<br /> <div class="container">
<table class="table" style="width: 50%; min-width: 250px;"> <div class="row">
<div class="col">
<table class="table table-borderless">
<tbody> <tbody>
<tr> <tr>
<th scope="col">@Localizer["Role"]</th> <th scope="col">@Localizer["Role"]</th>
@ -31,9 +34,18 @@
} }
</tbody> </tbody>
</table> </table>
<br />
</div>
</div>
<div class="row">
<div class="col">
@if (_users.Count != 0) @if (_users.Count != 0)
{ {
<table class="table" style="width: 50%; min-width: 250px;"> <div class="row">
<div class="col">
</div>
</div>
<table class="table table-borderless">
<thead> <thead>
<tr> <tr>
<th scope="col">@Localizer["User"]</th> <th scope="col">@Localizer["User"]</th>
@ -60,19 +72,31 @@
} }
</tbody> </tbody>
</table> </table>
<br />
} }
<table class="table" style="width: 50%; min-width: 250px;"> </div>
</div>
<div class="row">
<div class="col">
<table class="table table-borderless">
<tbody> <tbody>
<tr> <tr>
<td class="input-group"> <td class="input-group">
<input type="text" name="Username" class="form-control" placeholder="@Localizer["Enter Username"]" @bind="@_username" /> <input type="text" name="Username" class="form-control" placeholder="@Localizer["Username.Enter"]" @bind="@_username" />
<button type="button" class="btn btn-primary" @onclick="AddUser">@Localizer["Add"]</button> <button type="button" class="btn btn-primary" @onclick="AddUser">@SharedLocalizer["Add"]</button>
</td> </td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
<br /> <br />
</div>
</div>
<div class="row">
<div class="col">
<ModuleMessage Type="MessageType.Error" Message="@_message" /> <ModuleMessage Type="MessageType.Error" Message="@_message" />
</div>
</div>
</div>
} }
@code { @code {
@ -180,7 +204,7 @@
} }
catch catch
{ {
_message = Localizer["Username Does Not Exist"]; _message = Localizer["Message.Username.DontExist"];
} }
} }

View File

@ -14,7 +14,7 @@
<ModuleMessage Message="@_message" Type="MessageType.Warning"></ModuleMessage> <ModuleMessage Message="@_message" Type="MessageType.Warning"></ModuleMessage>
<br /> <br />
} }
<div class="row justify-content-center" style="margin-bottom: 20px;"> <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-secondary" @onclick="RefreshRichText">@Localizer["SynchronizeContent"]</button>&nbsp;&nbsp;
<button type="button" class="btn btn-primary" @onclick="InsertImage">@Localizer["InsertImage"]</button> <button type="button" class="btn btn-primary" @onclick="InsertImage">@Localizer["InsertImage"]</button>
@if (_filemanagervisible) @if (_filemanagervisible)
@ -66,7 +66,7 @@
</div> </div>
</TabPanel> </TabPanel>
<TabPanel Name="Raw" Heading="Raw HTML Editor" ResourceKey="HtmlEditor"> <TabPanel Name="Raw" Heading="Raw HTML Editor" ResourceKey="HtmlEditor">
<div class="row justify-content-center" style="margin-bottom: 20px;"> <div class="d-flex justify-content-center mb-2">
<button type="button" class="btn btn-secondary" @onclick="RefreshRawHtml">@Localizer["SynchronizeContent"]</button> <button type="button" class="btn btn-secondary" @onclick="RefreshRawHtml">@Localizer["SynchronizeContent"]</button>
</div> </div>
@if (ReadOnly) @if (ReadOnly)
@ -143,6 +143,8 @@
await interop.LoadEditorContent(_editorElement, Content); 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 ) // 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); _original = await interop.GetHtml(_editorElement);
} }
@ -200,7 +202,7 @@
} }
else else
{ {
_message = Localizer["You Must Select An Image To Insert"]; _message = Localizer["Message.Require.Image"];
} }
} }
else else

View File

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

View File

@ -10,13 +10,13 @@
<li class="nav-item" @key="tabPanel.Name"> <li class="nav-item" @key="tabPanel.Name">
@if (tabPanel.Name == ActiveTab) @if (tabPanel.Name == ActiveTab)
{ {
<a class="nav-link active" data-toggle="tab" href="#@tabPanel.Name" role="tab" @onclick:preventDefault="true"> <a class="nav-link active" data-bs-toggle="tab" href="#@tabPanel.Name" role="tab" @onclick:preventDefault="true">
@tabPanel.DisplayHeading() @tabPanel.DisplayHeading()
</a> </a>
} }
else else
{ {
<a class="nav-link" data-toggle="tab" href="#@tabPanel.Name" role="tab" @onclick:preventDefault="true"> <a class="nav-link" data-bs-toggle="tab" href="#@tabPanel.Name" role="tab" @onclick:preventDefault="true">
@tabPanel.DisplayHeading() @tabPanel.DisplayHeading()
</a> </a>
} }

View File

@ -7,12 +7,13 @@
@inject ISettingService SettingService @inject ISettingService SettingService
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject IStringLocalizer<Edit> Localizer @inject IStringLocalizer<Edit> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_content != null) @if (_content != null)
{ {
<RichTextEditor Content="@_content" AllowFileManagement="@_allowfilemanagement" @ref="@RichTextEditorHtml"></RichTextEditor> <RichTextEditor Content="@_content" AllowFileManagement="@_allowfilemanagement" @ref="@RichTextEditorHtml"></RichTextEditor>
<button type="button" class="btn btn-success" @onclick="SaveContent">@Localizer["Save"]</button> <button type="button" class="btn btn-success" @onclick="SaveContent">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@Localizer["Cancel"]</NavLink> <NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
@if (!string.IsNullOrEmpty(_content)) @if (!string.IsNullOrEmpty(_content))
{ {
<br /> <br />
@ -96,7 +97,7 @@
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Saving Content {Error}", ex.Message); 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 @namespace Oqtane.Modules.HtmlText
@inherits ModuleBase @inherits ModuleBase
@inject IHtmlTextService HtmlTextService @inject IHtmlTextService HtmlTextService
@inject IStringLocalizer<Index> Localizer
@((MarkupString)content) @((MarkupString)content)
@if (PageState.EditMode) @if (PageState.EditMode)
{ {
<br /><ActionLink Action="Edit" EditMode="true" ResourceKey="Edit" /><br /><br /> <br />
<ActionLink Action="Edit" EditMode="true" ResourceKey="Edit" />
<br />
<br />
} }
@code { @code {

View File

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

View File

@ -3,20 +3,19 @@
@inject ISettingService SettingService @inject ISettingService SettingService
@implements Oqtane.Interfaces.ISettingsControl @implements Oqtane.Interfaces.ISettingsControl
@inject IStringLocalizer<Settings> Localizer @inject IStringLocalizer<Settings> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer
<table class="table table-borderless"> <div class="container">
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="files" ResourceKey="Allow File Management" HelpText="Specify If Editors Can Upload and Select Files">Allow File Management: </Label>
<Label For="files" ResourceKey="Allow File Management" HelpText="Specify If Editors Can Upload and Select Files">Allow File Management: </Label> <div class="col-sm-9">
</td> <select id="files" class="form-select" @bind="@_allowfilemanagement">
<td> <option value="true">@SharedLocalizer["Yes"]</option>
<select id="files" class="form-control" @bind="@_allowfilemanagement"> <option value="false">@SharedLocalizer["No"]</option>
<option value="true">@Localizer["Yes"]</option>
<option value="false">@Localizer["No"]</option>
</select> </select>
</td> </div>
</tr> </div>
</table> </div>
@code { @code {
private string _allowfilemanagement; private string _allowfilemanagement;

View File

@ -84,11 +84,21 @@ namespace Oqtane.Modules
return NavigateUrl(path, ""); return NavigateUrl(path, "");
} }
public string NavigateUrl(bool refresh)
{
return NavigateUrl(PageState.Page.Path, refresh);
}
public string NavigateUrl(string path, string parameters) public string NavigateUrl(string path, string parameters)
{ {
return Utilities.NavigateUrl(PageState.Alias.Path, path, 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) public string EditUrl(string action)
{ {
return EditUrl(ModuleState.ModuleId, action); return EditUrl(ModuleState.ModuleId, action);
@ -124,6 +134,11 @@ namespace Oqtane.Modules
return Utilities.ContentUrl(PageState.Alias, fileid, asAttachment); 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 = "") public virtual Dictionary<string, string> GetUrlParameters(string parametersTemplate = "")
{ {
var urlParameters = new Dictionary<string, string>(); var urlParameters = new Dictionary<string, string>();
@ -195,6 +210,38 @@ namespace Oqtane.Modules
// logging methods // logging methods
public async Task Log(Alias alias, LogLevel level, string function, Exception exception, string message, params object[] args) public async Task Log(Alias alias, LogLevel level, string function, Exception exception, string message, params object[] args)
{
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 pageId = ModuleState.PageId;
int moduleId = ModuleState.ModuleId; int moduleId = ModuleState.ModuleId;
@ -205,34 +252,8 @@ namespace Oqtane.Modules
} }
string category = GetType().AssemblyQualifiedName; string category = GetType().AssemblyQualifiedName;
string feature = Utilities.GetTypeNameLastSegment(category, 1); 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": await LoggingService.Log(alias, pageId, moduleId, userId, category, feature, function, level, exception, message, args);
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);
} }
public class Logger public class Logger
@ -249,6 +270,11 @@ namespace Oqtane.Modules
await _moduleBase.Log(null, LogLevel.Trace, "", null, message, args); 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) public async Task LogTrace(Exception exception, string message, params object[] args)
{ {
await _moduleBase.Log(null, LogLevel.Trace, "", exception, message, 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); 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) public async Task LogDebug(Exception exception, string message, params object[] args)
{ {
await _moduleBase.Log(null, LogLevel.Debug, "", exception, message, 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); 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) public async Task LogInformation(Exception exception, string message, params object[] args)
{ {
await _moduleBase.Log(null, LogLevel.Information, "", exception, message, 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); 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) public async Task LogWarning(Exception exception, string message, params object[] args)
{ {
await _moduleBase.Log(null, LogLevel.Warning, "", exception, message, 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); 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) public async Task LogError(Exception exception, string message, params object[] args)
{ {
await _moduleBase.Log(null, LogLevel.Error, "", exception, message, 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); 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) public async Task LogCritical(Exception exception, string message, params object[] args)
{ {
await _moduleBase.Log(null, LogLevel.Critical, "", exception, message, args); await _moduleBase.Log(null, LogLevel.Critical, "", exception, message, args);

View File

@ -5,7 +5,7 @@
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<RazorLangVersion>3.0</RazorLangVersion> <RazorLangVersion>3.0</RazorLangVersion>
<Configurations>Debug;Release</Configurations> <Configurations>Debug;Release</Configurations>
<Version>2.1.0</Version> <Version>2.3.0</Version>
<Product>Oqtane</Product> <Product>Oqtane</Product>
<Authors>Shaun Walker</Authors> <Authors>Shaun Walker</Authors>
<Company>.NET Foundation</Company> <Company>.NET Foundation</Company>
@ -13,7 +13,7 @@
<Copyright>.NET Foundation</Copyright> <Copyright>.NET Foundation</Copyright>
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl> <PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl> <PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v2.1.0</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v2.3.0</PackageReleaseNotes>
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl> <RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
<RepositoryType>Git</RepositoryType> <RepositoryType>Git</RepositoryType>
<RootNamespace>Oqtane</RootNamespace> <RootNamespace>Oqtane</RootNamespace>
@ -33,4 +33,20 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Oqtane.Shared\Oqtane.Shared.csproj" /> <ProjectReference Include="..\Oqtane.Shared\Oqtane.Shared.csproj" />
</ItemGroup> </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> </Project>

View File

@ -8,14 +8,11 @@ using System.Net.Http;
using System.Reflection; using System.Reflection;
using System.Runtime.Loader; using System.Runtime.Loader;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.AspNetCore.Localization; using Microsoft.AspNetCore.Localization;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.JSInterop; using Microsoft.JSInterop;
using Oqtane.Interfaces;
using Oqtane.Modules; using Oqtane.Modules;
using Oqtane.Providers;
using Oqtane.Services; using Oqtane.Services;
using Oqtane.Shared; using Oqtane.Shared;
using Oqtane.UI; using Oqtane.UI;
@ -28,7 +25,8 @@ namespace Oqtane.Client
{ {
var builder = WebAssemblyHostBuilder.CreateDefault(args); var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("app"); 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.AddSingleton(httpClient);
builder.Services.AddOptions(); builder.Services.AddOptions();
@ -37,38 +35,10 @@ namespace Oqtane.Client
builder.Services.AddLocalization(options => options.ResourcesPath = "Resources"); builder.Services.AddLocalization(options => options.ResourcesPath = "Resources");
// register auth services // register auth services
builder.Services.AddAuthorizationCore(); builder.Services.AddOqtaneAuthorization();
builder.Services.AddScoped<IdentityAuthenticationStateProvider>();
builder.Services.AddScoped<AuthenticationStateProvider>(s => s.GetRequiredService<IdentityAuthenticationStateProvider>());
// register scoped core services // register scoped core services
builder.Services.AddScoped<SiteState>(); builder.Services.AddOqtaneScopedServices();
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>();
await LoadClientAssemblies(httpClient); await LoadClientAssemblies(httpClient);
@ -76,38 +46,15 @@ namespace Oqtane.Client
foreach (var assembly in assemblies) foreach (var assembly in assemblies)
{ {
// dynamically register module services // dynamically register module services
var implementationTypes = assembly.GetInterfaces<IService>(); RegisterModuleServices(assembly, builder.Services);
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);
}
}
// register client startup services // register client startup services
var startUps = assembly.GetInstances<IClientStartup>(); RegisterClientStartups(assembly, builder.Services);
foreach (var startup in startUps)
{
startup.ConfigureServices(builder.Services);
}
} }
var host = builder.Build(); 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))) await SetCultureFromLocalizationCookie(host.Services);
{
culture = cultures.Single(c => c.IsDefault).Name;
}
SetCulture(culture);
ServiceActivator.Configure(host.Services); ServiceActivator.Configure(host.Services);
@ -163,6 +110,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) private static void SetCulture(string culture)
{ {
var cultureInfo = CultureInfo.GetCultureInfo(culture); var cultureInfo = CultureInfo.GetCultureInfo(culture);

View File

@ -30,14 +30,11 @@ namespace Oqtane.Providers
// 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 ) // 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 http = _serviceProvider.GetRequiredService<HttpClient>();
// get alias as SiteState has not been initialized ( cannot use AliasService as it is not yet registered ) var siteState = _serviceProvider.GetRequiredService<SiteState>();
var path = new Uri(_navigationManager.Uri).LocalPath.Substring(1); User user = await http.GetFromJsonAsync<User>(Utilities.TenantUrl(siteState.Alias, "/api/User/authenticate"));
var alias = await http.GetFromJsonAsync<Alias>($"/api/Alias/name/?path={WebUtility.UrlEncode(path)}&sync={DateTime.UtcNow.ToString("yyyyMMddHHmmssfff")}");
// get user
User user = await http.GetFromJsonAsync<User>(Utilities.TenantUrl(alias, "/api/User/authenticate"));
if (user.IsAuthenticated) if (user.IsAuthenticated)
{ {
identity = UserSecurity.CreateClaimsIdentity(alias, user); identity = UserSecurity.CreateClaimsIdentity(siteState.Alias, user);
} }
return new AuthenticationState(new ClaimsPrincipal(identity)); 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,156 @@
<?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>
</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>

View File

@ -0,0 +1,189 @@
<?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="Minute(s)" xml:space="preserve">
<value>Minute(s)</value>
</data>
<data name="Hour(s)" xml:space="preserve">
<value>Hour(s)</value>
</data>
<data name="Day(s)" xml:space="preserve">
<value>Day(s)</value>
</data>
<data name="Month(s)" xml:space="preserve">
<value>Month(s)</value>
</data>
<data name="Error.Job.Load" xml:space="preserve">
<value>Error Loading Job</value>
</data>
<data name="Error.Job.Update" xml:space="preserve">
<value>Error Updating Job</value>
</data>
<data name="Message.Required.JobInfo" xml:space="preserve">
<value>You Must Provide The Job Name, Type, Frequency, and Retention</value>
</data>
<data name="Name.HelpText" xml:space="preserve">
<value>Enter the job name</value>
</data>
<data name="Type.HelpText" xml:space="preserve">
<value>The fully qualified job type name</value>
</data>
<data name="Enabled.HelpText" xml:space="preserve">
<value>Select whether you want the job enabled or not</value>
</data>
<data name="RunsEvery.HelpText" xml:space="preserve">
<value>Select how often you want the job to run</value>
</data>
<data name="Starting.HelpText" xml:space="preserve">
<value>Optionally enter the date and time when this job should start executing</value>
</data>
<data name="Ending.HelpText" xml:space="preserve">
<value>Optionally enter the date and time when this job should stop executing</value>
</data>
<data name="RetentionLog.HelpText" xml:space="preserve">
<value>Number of log entries to retain for this job</value>
</data>
<data name="NextExecution.HelpText" xml:space="preserve">
<value>Optionally modify the date and time when this job should execute next</value>
</data>
<data name="Type.Text" xml:space="preserve">
<value>Type: </value>
</data>
<data name="Enabled.Text" xml:space="preserve">
<value>Enabled? </value>
</data>
<data name="RunsEvery.Text" xml:space="preserve">
<value>Runs Every: </value>
</data>
<data name="Starting.Text" xml:space="preserve">
<value>Starting: </value>
</data>
<data name="Ending.Text" xml:space="preserve">
<value>Ending: </value>
</data>
<data name="RetentionLog.Text" xml:space="preserve">
<value>Retention Log (Items): </value>
</data>
<data name="NextExecution.Text" xml:space="preserve">
<value>Next Execution: </value>
</data>
</root>

View File

@ -0,0 +1,171 @@
<?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="NextExecution" xml:space="preserve">
<value>Next Execution</value>
</data>
<data name="Disabled" xml:space="preserve">
<value>Disabled</value>
</data>
<data name="Executing" xml:space="preserve">
<value>Executing</value>
</data>
<data name="Idle" xml:space="preserve">
<value>Idle</value>
</data>
<data name="Every" xml:space="preserve">
<value>Every</value>
</data>
<data name="Minute" xml:space="preserve">
<value>Minute</value>
</data>
<data name="Hour" xml:space="preserve">
<value>Hour</value>
</data>
<data name="Day" xml:space="preserve">
<value>Day</value>
</data>
<data name="Month" xml:space="preserve">
<value>Month</value>
</data>
<data name="s" xml:space="preserve">
<value>s</value>
</data>
<data name="Error.Job.Delete" xml:space="preserve">
<value>Error Deleting Job</value>
</data>
<data name="ViewJobs.Text" xml:space="preserve">
<value>View Logs</value>
</data>
<data name="DeleteJob.Header" xml:space="preserve">
<value>Delete Job</value>
</data>
<data name="DeleteJob.Message" xml:space="preserve">
<value>Are You Sure You Wish To Delete This Job?</value>
</data>
<data name="Frequency" xml:space="preserve">
<value>Frequency</value>
</data>
<data name="Start" xml:space="preserve">
<value>Start</value>
</data>
<data name="Stop" xml:space="preserve">
<value>Stop</value>
</data>
</root>

View File

@ -0,0 +1,135 @@
<?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="Executing" xml:space="preserve">
<value>Executing</value>
</data>
<data name="Started" xml:space="preserve">
<value>Started</value>
</data>
<data name="Finished" xml:space="preserve">
<value>Finished</value>
</data>
<data name="Succeeded" xml:space="preserve">
<value>Succeeded</value>
</data>
<data name="Failed" xml:space="preserve">
<value>Failed</value>
</data>
</root>

View File

@ -0,0 +1,156 @@
<?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="The Only Supported Culture That Has Been Defined Is English" xml:space="preserve">
<value>The Only Supported Culture That Has Been Defined Is English</value>
</data>
<data name="Error.Language.Add" xml:space="preserve">
<value>Error Adding Language</value>
</data>
<data name="Name.HelpText" xml:space="preserve">
<value>Name Of The Langauage</value>
</data>
<data name="IsDefault.HelpText" xml:space="preserve">
<value>Indicates Whether Or Not This Language Is The Default For The Site</value>
</data>
<data name="Name.Text" xml:space="preserve">
<value>Name:</value>
</data>
<data name="IsDefault.Text" xml:space="preserve">
<value>Default?</value>
</data>
<data name="AllLanguages" xml:space="preserve">
<value>All The Installed Languages Have Been Added.</value>
</data>
<data name="Error.Language.Download" xml:space="preserve">
<value>Error Downloading Translation</value>
</data>
<data name="OnlyEnglish" xml:space="preserve">
<value>The Only Installed Language Is English</value>
</data>
<data name="Success.Language.Download" xml:space="preserve">
<value>Translation Downloaded Successfully. Click Install To Complete Installation.</value>
</data>
<data name="Success.Language.Install" xml:space="preserve">
<value>Translations Installed Successfully. You Must &lt;a href={0}&gt;Restart&lt;/a&gt; Your Application To Apply These Changes.</value>
</data>
<data name="Search.NoResults" xml:space="preserve">
<value>No Translations Match The Criteria Provided Or Package Service Is Disabled</value>
</data>
</root>

View File

@ -0,0 +1,144 @@
<?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="Code" xml:space="preserve">
<value>Code</value>
</data>
<data name="Confirm.Language.Delete" xml:space="preserve">
<value>Are You Sure You Wish To Delete The {0} Language?</value>
</data>
<data name="Error.Language.Delete" xml:space="preserve">
<value>Error Deleting Language</value>
</data>
<data name="AddLanguage.Text" xml:space="preserve">
<value>Add Language</value>
</data>
<data name="DeleteLanguage.Header" xml:space="preserve">
<value>Delete Language</value>
</data>
<data name="Error.Language.Download" xml:space="preserve">
<value>Error Downloading Translation</value>
</data>
<data name="Success.Language.Install" xml:space="preserve">
<value>Translation Installed Successfully. You Must &lt;a href={0}&gt;Restart&lt;/a&gt; Your Application To Apply These Changes.</value>
</data>
<data name="Default" xml:space="preserve">
<value>Default</value>
</data>
</root>

View File

@ -0,0 +1,141 @@
<?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="RememberMe" xml:space="preserve">
<value>Remember Me?</value>
</data>
<data name="ForgotPassword" xml:space="preserve">
<value>Forgot Password</value>
</data>
<data name="Success.Account.Verified" xml:space="preserve">
<value>User Account Verified Successfully. You Can Now Login With Your Username And Password Below.</value>
</data>
<data name="Message.Account.NotVerfied" xml:space="preserve">
<value>User Account Could Not Be Verified. Please Contact Your Administrator For Further Instructions.</value>
</data>
<data name="Error.Login.Fail" xml:space="preserve">
<value>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.</value>
</data>
<data name="Message.Required.UserInfo" xml:space="preserve">
<value>Please Provide Your Username And Password</value>
</data>
<data name="Info.SignedIn" xml:space="preserve">
<value>You Are Already Signed In</value>
</data>
</root>

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