Compare commits

..

178 Commits

Author SHA1 Message Date
9d0770e360 Merge pull request #1696 from oqtane/master
Merge pull request #1695 from oqtane/dev
2021-09-27 14:04:23 -04:00
088cb2a30e Merge pull request #1695 from oqtane/dev
2.3.1 release
2021-09-27 14:03:55 -04:00
4f61dd7bb3 Merge pull request #1694 from sbwalker/dev
increment version to 2.3.1
2021-09-27 11:37:34 -04:00
c2be84a367 increment version to 2.3.1 2021-09-27 11:43:57 -04:00
30fb6fd8e2 Merge pull request #1693 from sbwalker/dev
fix #1691 - AntiForgeryToken header not being set during startup
2021-09-27 08:37:27 -04:00
4bfb5d9f34 fix #1691 - AntiForgeryToken header not being set during startup 2021-09-27 08:44:16 -04:00
023f29491d Update README.md 2021-09-25 09:08:20 -04:00
5166fe2b41 Update README.md 2021-09-25 09:04:36 -04:00
71aa41d55e Update README.md 2021-09-25 09:03:44 -04:00
f6fd50f449 Update README.md 2021-09-25 09:02:06 -04:00
81e2f7c288 Update README.md 2021-09-25 09:01:40 -04:00
895d5e50de Merge pull request #1689 from oqtane/master
Merge pull request #1688 from oqtane/dev
2021-09-24 17:12:22 -04:00
6cd1b1ceaa Merge pull request #1688 from oqtane/dev
2.3.0 release
2021-09-24 17:11:32 -04:00
82ae9409f1 Merge branch 'dev' of https://github.com/sbwalker/oqtane.framework into dev 2021-09-24 17:07:04 -04:00
764b879a77 changes to build/publish params for WebAssembly 2021-09-24 17:06:45 -04:00
d3e71b6a7e Merge pull request #1687 from sbwalker/dev
changes to build/publish params for WebAssembly
2021-09-24 17:00:39 -04:00
80d23d1c95 Add paging to SQL Manager results 2021-09-23 18:02:15 -04:00
531e89346e Merge pull request #1685 from sbwalker/dev
Add paging to SQL Manager results
2021-09-23 17:55:44 -04:00
f220cb52bb Merge pull request #1682 from gjwalk/dev
Sites Validation
2021-09-23 17:12:36 -04:00
58ff42f813 Merge pull request #1683 from nicpitsch/pr_user-management-profile
Profile properties as dropdown in User Management
2021-09-23 17:10:33 -04:00
6b82b03bf1 Merge pull request #1684 from sbwalker/dev
Use ComponentTagHelper parameters on Blazor Server for passing state to allow pre-rendering to function properly ( ComponentTagHelper parameters do not work on Blazor WebAssembly - likely a .NET 5 bug )
2021-09-23 17:10:24 -04:00
005843ef2d Use ComponentTagHelper parameters on Blazor Server for passing state to allow pre-rendering to function properly ( ComponentTagHelper parameters do not work on Blazor WebAssembly - likely a .NET 5 bug ) 2021-09-23 17:16:51 -04:00
10917644ab Profile properties as dropdown in User Management (same as User Profile). 2021-09-23 10:05:24 +02:00
9fa3ade832 Sites Validation 2021-09-22 18:22:30 -04:00
e10135271d Update README.md 2021-09-22 16:37:57 -04:00
c1b482e0c0 check in latest database provider packages 2021-09-22 14:47:14 -04:00
a3d7760a09 Merge pull request #1681 from sbwalker/dev
check in latest database provider packages
2021-09-22 14:40:54 -04:00
57db7c1efc update version to 2.3.0 in preparation for release 2021-09-22 11:55:01 -04:00
586c9b6db6 Merge pull request #1680 from sbwalker/dev
update version to 2.3.0 in preparation for release
2021-09-22 11:48:23 -04:00
58a86b67ee fix #1672 - releases need to be published with IL trimming disabled or else dynamic methods will be stripped. Unfortunately compression needs to be disabled as well as if it is not, a "None of the “sha256” hashes in the integrity attribute match the content of the subresource." error is thrown. This seems to be a bug - which I will pursue with Microsoft. 2021-09-22 10:24:13 -04:00
43ebf50b61 Merge pull request #1679 from sbwalker/dev
fix #1672 - releases need to be published with IL trimming disabled or else dynamic methods will be stripped. Unfortunately compression needs to be disabled as well as if it is not, a "None of the “sha256” hashes in the integrity attribute match the content of the subresource." error is thrown. This seems to be a bug - which I will pursue with Microsoft.
2021-09-22 10:17:48 -04:00
5071cf4752 modify method for determining Runtime in SiteRouter as ComponentTagHelper "param-" appears to only work on Blazor Server - not on WebAssembly 2021-09-21 12:48:15 -04:00
00e2e79fc5 Merge pull request #1676 from sbwalker/dev
modify method for determining Runtime in SiteRouter as ComponentTagHelper "param-" appears to only work on Blazor Server - not on WebAssembly
2021-09-21 12:41:31 -04:00
8d37444755 improve validation for public site settings 2021-09-21 07:45:43 -04:00
20d81bee00 Merge pull request #1675 from sbwalker/dev
improve validation for public site settings
2021-09-21 07:39:35 -04:00
ca387d7b26 fix Oqtane theme settings for page scope 2021-09-20 17:23:56 -04:00
a0580f6861 Merge pull request #1674 from sbwalker/dev
fix Oqtane theme settings for page scope
2021-09-20 17:17:09 -04:00
f739db1e42 Enhance Settings API for public Site Settings. Added Settings to Site model by default. Added new parameters to Login and UserProfile components. Enhanced Oqtane Theme settings to use new component parameters. Enhanced image download and resizing logic. 2021-09-20 17:15:52 -04:00
d5bfe7cfbd Merge pull request #1673 from sbwalker/dev
Enhance Settings API for public Site Settings. Added Settings to Site model by default. Added new parameters to Login and UserProfile components. Enhanced Oqtane Theme settings to use new component parameters. Enhanced image download and resizing logic.
2021-09-20 17:09:52 -04:00
db85e088bf fix #1659 installation issue on PostgreSQL by ntroducing a new RewriteValue method which can be overridden in a database provider to provide custom behavior. Updated PostgreSQL provide to utilize new method. Also added an Oqtane.Server project reference to the module and theme external templates to streamline the development experience (credit @leighpointer). 2021-09-17 13:56:19 -04:00
d053901b32 Merge pull request #1670 from 2sic-forks/dev
Documentation only
2021-09-17 13:50:02 -04:00
2957c7d6a9 Merge pull request #1671 from sbwalker/dev
fix #1659 installation issue on PostgreSQL by ntroducing a new RewriteValue method which can be overridden in a database provider to provide custom behavior. Updated PostgreSQL provide to utilize new method. Also added an Oqtane.Server project reference to the module and theme external templates to streamline the development experience (credit @leighpointer).
2021-09-17 13:49:52 -04:00
a69d52a389 undo accidental / internal commit 2021-09-17 15:40:05 +02:00
7b5dbbd7ed undo change by 2sic 2021-09-17 15:36:23 +02:00
6d0fb6ca80 undo change by 2sxc 2021-09-17 15:35:43 +02:00
b4f7344ae4 removed unnecessary message from top of module, theme, language installation pages 2021-09-17 09:28:27 -04:00
a5cecb7354 Merge pull request #1669 from sbwalker/dev
removed unnecessary message from top of module, theme, language installation pages
2021-09-17 09:21:57 -04:00
406a15c5bd constrain file logger size 2021-09-17 09:17:42 -04:00
ed0408de95 Merge pull request #1668 from sbwalker/dev
constrain file logger size
2021-09-17 09:11:15 -04:00
b5bba1fd11 improved method for determining Runtime in SiteRouter 2021-09-17 09:06:27 -04:00
9065bad2bb Merge pull request #1667 from sbwalker/dev
improved method for determining Runtime in SiteRouter
2021-09-17 09:00:06 -04:00
289034fd4f fix #1640 - prevent UX "flicker" which was caused by pre-rendering 2021-09-17 08:48:01 -04:00
f052f6696f Merge pull request #1666 from sbwalker/dev
fix #1640 - prevent UX "flicker" which was caused by pre-rendering
2021-09-17 08:41:44 -04:00
267ca178ed added basic xml comments to all Oqtane.Services interfaces 2021-09-17 10:13:26 +02:00
c01c16c7bc Merge pull request #1660 from gjwalk/dev
site validation
2021-09-16 18:00:50 -04:00
2ac069082d Merge pull request #1663 from sbwalker/dev
file manager component improvements
2021-09-16 17:58:20 -04:00
f00ea09b6e file manager component improvements 2021-09-16 18:04:50 -04:00
b9259ce6ca added optional event callback delegates to FileManager component to allow calling components to be notified on upload, change, or delete 2021-09-16 07:59:36 -04:00
0490638657 Merge pull request #1662 from sbwalker/dev
added optional event callback delegates to FileManager component to allow calling components to be notified on upload, change, or delete
2021-09-16 07:53:31 -04:00
467b6ba9da site validation 2021-09-15 19:10:14 -04:00
423a42d25b fixed invalid XML comment 2021-09-15 17:07:40 +02:00
b496dc488c Merge branch 'oqtane-dev' into dev 2021-09-15 15:23:28 +02:00
9750723035 Merge branch 'dev' of https://github.com/oqtane/oqtane.framework into oqtane-dev
# Conflicts:
#	Oqtane.Client/App.razor
2021-09-15 15:23:16 +02:00
a4f147b547 Merge pull request #1655 from gjwalk/dev
roles validation
2021-09-15 07:58:21 -04:00
0827fedb74 Merge pull request #1658 from sbwalker/dev
Added support for File descriptions, Folder capacity and image sizes. Added image resizing capability using ImageSharp - implemented in user profile. Added parameter to disable image preview in FileManager component. Overhauled Pager component and added Columns parameter for Grid mode. Populated PageState.User.IsAuthenticated in SiteRouter. Added support for zero price commercial extentions.
2021-09-15 07:57:06 -04:00
898b908c1b Added support for File descriptions, Folder capacity and image sizes. Added image resizing capability using ImageSharp - implemented in user profile. Added parameter to disable image preview in FileManager component. Overhauled Pager component and added Columns parameter for Grid mode. Populated PageState.User.IsAuthenticated in SiteRouter. Added support for zero price commercial extentions. 2021-09-15 08:02:55 -04:00
f21b70a51e roles validation 2021-09-11 18:18:23 -04:00
ba7524b754 Merge pull request #1651 from leigh-pointer/RecycleCheck
Validate if Page in Recycle Bin During Creation
2021-09-10 13:06:59 -04:00
eb068a8d53 Merge pull request #1652 from sbwalker/dev
fix #1647 - module reordering on page issue
2021-09-10 13:05:35 -04:00
14fbc3a5b4 fix #1647 - module reordering on page issue 2021-09-10 13:12:00 -04:00
d2fa8902f9 Auto stash before rebase of "origin/RecycleCheck"
correction
2021-09-10 18:59:23 +02:00
53e5728ad2 fix #1640 to resolve issue with server prerendering, upgrade Installer to Bootstrap5, add more defensive logic and logging to DatabaseManager, fix file logger issue, update Pager to use Bootstrap5 pagination, add expiry date support for commercial extensions 2021-09-10 08:24:05 -04:00
dd7de055f6 Merge pull request #1650 from sbwalker/dev
fix #1640 to resolve issue with server prerendering,  upgrade Installer to Bootstrap5, add more defensive logic and logging to DatabaseManager, fix file logger issue, update Pager to use Bootstrap5 pagination, add expiry date support for commercial extensions
2021-09-10 08:17:52 -04:00
3cd7249750 Page create - Recycle Bin Check
After Delete Page, Cant create page of same name #1645 issue. Added check and message if the page is in the recycle bin.
2021-09-08 08:08:24 +02:00
07165ce68d add support for trial periods 2021-09-03 15:24:51 -04:00
2c0fe71f14 Merge pull request #1642 from sbwalker/dev
add support for trial periods
2021-09-03 15:18:58 -04:00
c74a53b301 update fix https://github.com/oqtane/oqtane.framework/issues/1640 2021-09-02 20:50:16 +02:00
3663f723e7 fix https://github.com/oqtane/oqtane.framework/issues/1640 2021-09-02 20:32:04 +02:00
233da1508b Replacing dependency on System.Drawing with SixLabors.ImageSharp based on cross platform guidance from Microsoft 9b4520703c/accepted/2021/system-drawing-win-only/system-drawing-win-only.md 2021-09-02 11:58:31 -04:00
ad34e9aeb8 Merge pull request #1639 from sbwalker/dev
Replacing dependency on System.Drawing with SixLabors.ImageSharp based on cross platform guidance from Microsoft 9b4520703c/accepted/2021/system-drawing-win-only/system-drawing-win-only.md
2021-09-02 11:54:40 -04:00
d29dc9d036 make containing class overridable in Control Panel ( header and body are already overridable ) 2021-09-01 09:13:09 -04:00
d71030fdef Merge pull request #1638 from sbwalker/dev
make containing class overridable in Control Panel ( header and body are already overridable )
2021-09-01 09:07:02 -04:00
bb5ca475d3 fix #1628 - make DBContext Transient, modify Control Panel to use standard Bootstrap 5 offcanvas classes, add auto trimming to file logger, fix issue in File Repository related to populating Url on Add/Update. 2021-09-01 09:01:11 -04:00
01c7a8fcdb Merge pull request #1637 from sbwalker/dev
fix #1628 - make DBContext Transient, modify Control Panel to use standard Bootstrap 5 offcanvas classes, add auto trimming to file logger, fix issue in File Repository related to populating Url on Add/Update.
2021-09-01 08:55:06 -04:00
f6c46878c6 add new overloads to client-side logging methods to include LogFunction enum parameter so that it can be specified explicitly rather than only being inferred from the page action 2021-08-30 08:46:53 -04:00
acda6bba74 Merge pull request #1634 from gjwalk/dev
reset validation
2021-08-30 08:42:23 -04:00
c8ee066608 Merge pull request #1635 from sbwalker/dev
add new overloads to client-side logging methods to include LogFunction enum parameter so that it can be specified explicitly rather than only being inferred from the page action
2021-08-30 08:40:55 -04:00
ca9fffaa71 reset validation 2021-08-29 21:03:35 -04:00
e00b7c9be9 add some missing localization keys 2021-08-27 17:29:45 -04:00
ee1a2b1810 Merge pull request #1631 from sbwalker/dev
add some missing localization keys
2021-08-27 17:23:37 -04:00
14266a99b3 Merge pull request #1614 from gjwalk/dev
register validation
2021-08-27 12:57:09 -04:00
7b105107cc Merge pull request #1630 from sbwalker/dev
fix #1617 convert line break to comma when saving aliases, improve license acceptance user experience
2021-08-27 08:11:54 -04:00
bb75242a4f fix #1617 convert line break to comma when saving aliases, improve license acceptance user experience 2021-08-27 08:17:48 -04:00
39ccc30680 fix Type label in Add Folder UI, make Profile description required, fix misc Bootstrap 5 cosmetic issues, fix #1618 Alias case sensitivity in router, fix File add and update methods so they return Url, fix UrlCombine helper method to use proper slash, enhance package installation to support commercial options 2021-08-26 18:20:58 -04:00
41651075e6 Merge pull request #1627 from horacioj/typo-Message.Required.Smtp
Fix typo for RESX message Message.Required.Smtp
2021-08-26 18:15:08 -04:00
e3af463e65 Merge pull request #1629 from sbwalker/dev
fix Type label in Add Folder UI, make Profile description required, fix misc Bootstrap 5 cosmetic issues, fix #1618 Alias case sensitivity in router, fix File add and update methods so they return Url, fix UrlCombine helper method to use proper slash, enhance package installation to support commercial options
2021-08-26 18:14:59 -04:00
5cbb8b1fa3 Fix typo for RESX message Message.Required.Smtp 2021-08-24 17:05:08 -03:00
2a7b74a0e1 Merge pull request #1622 from oqtane/revert-1603-dev
Revert "EntityBuilder UpdateColumn to dynamic type of value"
2021-08-20 16:32:18 -04:00
21fc493322 Revert "EntityBuilder UpdateColumn to dynamic type of value" 2021-08-20 16:32:04 -04:00
ea85eae4ce register validation 2021-08-18 10:55:34 -04:00
35ee97c145 Update LICENSE 2021-08-17 10:02:00 -04:00
097318cf9e make profile category optional 2021-08-17 08:13:17 -04:00
31d4f3d1b3 Merge pull request #1613 from sbwalker/dev
make profile category optional
2021-08-17 08:07:29 -04:00
83b3235a6b Merge pull request #1611 from gjwalk/dev
profiles validation
2021-08-17 08:02:57 -04:00
e47fc64c33 profiles validation 2021-08-16 14:37:11 -04:00
151a49f1ec Merge pull request #1610 from sbwalker/dev
fix #1607 - issue with setting Site Root when adding/editing a page
2021-08-16 11:33:05 -04:00
b78644f7e2 fix #1607 - issue with setting Site Root when adding/editing a page 2021-08-16 11:39:00 -04:00
d744e36dde Merge pull request #1603 from tomsync/dev
EntityBuilder UpdateColumn to dynamic type of value
2021-08-16 09:42:04 -04:00
90f4bd5120 Merge pull request #1605 from gjwalk/dev
pages validation
2021-08-16 09:41:09 -04:00
b436fcf749 Merge pull request #1609 from sbwalker/dev
support for commercial modules, themes, translations
2021-08-16 09:40:58 -04:00
ffcc229c78 support for commercial modules, themes, translations 2021-08-16 09:46:02 -04:00
ffe724b32d add support for free/paid in module, theme, translation installation 2021-08-13 15:56:22 -04:00
154d32cd31 Merge pull request #1606 from sbwalker/dev
add support for free/paid in module, theme, translation installation
2021-08-13 15:50:39 -04:00
7f056277ae pages validation 2021-08-12 14:48:23 -04:00
b19cbf54e0 add error handling to module export 2021-08-12 14:47:51 -04:00
aef0e363d8 Merge pull request #1604 from sbwalker/dev
add error handling to module export
2021-08-12 14:42:35 -04:00
6324034259 Merge pull request #1600 from gjwalk/dev
modules validation
2021-08-12 14:21:20 -04:00
5a1bada10c EntityBuilder UpdateColumn to dynamic type of value 2021-08-12 16:59:15 +07:00
ef90305bd7 modules validation 2021-08-09 12:38:12 -04:00
fe06a29ad2 Merge pull request #1594 from gjwalk/dev
moduleDefinitions validation
2021-08-09 11:24:00 -04:00
f6ae9822f3 Merge pull request #1599 from sbwalker/dev
fix #1593 - remove duplicate form tag from edit.razor
2021-08-09 11:21:56 -04:00
eb3e4916a8 fix #1593 - remove duplicate form tag from edit.razor 2021-08-09 11:27:39 -04:00
5a5535ea98 format license terms 2021-08-06 15:28:48 -04:00
4f8d0b1e7a Merge pull request #1596 from sbwalker/dev
format license terms
2021-08-06 15:23:22 -04:00
b1d64eac88 moduleDefinitions validation 2021-08-06 13:32:40 -04:00
04673a4804 Merge pull request #1591 from gjwalk/dev
moduleCreator validation
2021-08-06 12:55:17 -04:00
4d2f9d038a Merge pull request #1592 from sbwalker/dev
allow host username to be specified during installation, allow user to be added to host role, refresh user list after delete, improve date/time entry in scheduled jobs, require license acceptance during module and theme install
2021-08-06 12:54:39 -04:00
e4201c1a4d allow host username to be specified during installation, allow user to be added to host role, refresh user list after delete, improve date/time entry in scheduled jobs, require license acceptance during module and theme install 2021-08-06 12:59:56 -04:00
030c001371 moduleCreator validation 2021-08-06 11:44:52 -04:00
bd351f770b Merge pull request #1588 from gjwalk/dev
langauges validation
2021-08-06 08:56:47 -04:00
63093cca3b langauges validation 2021-08-05 12:24:11 -04:00
5c42e8e5bc Merge pull request #1587 from gjwalk/dev
jobs validation
2021-08-05 08:38:18 -04:00
153934b311 Merge pull request #1582 from leigh-pointer/1579
Fixes #1579 Exception when browsing to /login when you are already logged in
2021-08-05 08:38:05 -04:00
4b1ead1a36 jobs validation 2021-08-04 19:15:52 -04:00
ddafd21706 Fixes #1579 Exception when browsing to /login when you are already logged in
Added PageState.User check
2021-08-04 19:33:54 +02:00
6059a944bf Merge pull request #1577 from gjwalk/dev
files validation
2021-08-03 08:45:48 -04:00
1cc26c3902 files validation 2021-07-31 16:59:03 -04:00
00ca3d856b reset admin 2021-07-31 15:09:14 -04:00
9af8ab92c9 themes - users validation changes 2021-07-29 16:54:32 -04:00
46fcfcc321 reset - systemInfo validation changes 2021-07-29 16:52:27 -04:00
cf40462531 moduleDefintions - profiles validation changes 2021-07-29 16:46:58 -04:00
2dbf9671d9 dashboard - moduleCretaor changes 2021-07-29 16:38:36 -04:00
e42a687c9b fixing spacing 2021-07-29 15:28:15 -04:00
72c154b048 fix appsettings 2021-07-27 21:17:13 -04:00
b0921513c1 fix to theme 2021-07-27 21:12:18 -04:00
33a76c61ca updated modules for input requirements 2021-07-27 16:24:01 -04:00
c0254d0b92 Merge pull request #1567 from ADefWebserver/dev
Removes writing database connection string to appsettings.json in AzureDeploy.json
2021-07-22 09:21:19 -04:00
99d05fe4f9 Update README.md 2021-07-21 06:56:10 -07:00
f8ba6a0c5f Update README.md
Temporarily pointing to my Dev version of azuredeploy (do not merge)
2021-07-21 06:18:34 -07:00
3a58ed63e9 Update azuredeploy.json
https://github.com/oqtane/oqtane.framework/issues/1547
2021-07-21 06:14:42 -07:00
8aac801af2 Merge pull request #1566 from sbwalker/dev
update install wizard to Bootstrap 5
2021-07-19 16:44:43 -04:00
1cc16a7ed4 update install wizard to Bootstrap 5 2021-07-19 16:49:31 -04:00
7fe04d9651 Merge pull request #1564 from nicpitsch/dev
Copy external Theme to Oqtane.Server.
2021-07-19 16:44:34 -04:00
01a9dc4d2d Merge pull request #1562 from leigh-pointer/OffCanvas
user experience updates
2021-07-19 16:44:25 -04:00
1456462ca8 Copy external Theme to Oqtane.Server. 2021-07-19 20:53:45 +02:00
77415dc0e0 user experience updates 2021-07-18 17:30:32 +02:00
696b1970fc Update README.md 2021-07-18 11:15:03 -04:00
0e6baae366 Merge pull request #1554 from leigh-pointer/OffCanvas
ConltolPanel to use Bootstrap Offcanvas component
2021-07-18 10:42:09 -04:00
437e9ee43b Modifcations for ControlPanel BS5
current styles in app.css and theme.css for app-controlpanel removed as no longer needed.

Removed the _display var as that is no longer beign used.
2021-07-15 16:28:12 +02:00
6546f47bb2 Merge pull request #1556 from leigh-pointer/UsernameReadOnly 2021-07-15 09:47:51 -04:00
88c866057f Fix for #1555 Username readonly on Register form
Removed the readonly attribute from the username field.
2021-07-15 15:41:20 +02:00
3521dd41cd ConltolPanel to use Bootstrap Offcanvas component 2021-07-14 10:18:39 +02:00
dff3e6aaca Merge pull request #1550 from leigh-pointer/ModificationsBS5 2021-07-12 10:24:42 -04:00
4b04e6c8dc Merge pull request #1551 from leigh-pointer/ExtModDiv 2021-07-12 10:23:57 -04:00
37672ea632 Modified External Module Template
replace tables in markup with responsive approach.
2021-07-11 14:27:18 +02:00
31f35ad902 Modifications for Bootstrap 5
replace tables in markup with responsive approach
2021-07-11 14:16:33 +02:00
0b7b34f336 Merge pull request #1549 from leigh-pointer/Bootstrap5
modifications for Bootstrap 5
2021-07-10 19:35:41 -04:00
fca290f8f5 Modifications for Bootstrap 5
Admin section now finished.  All Tables now replaced with div
2021-07-10 13:37:05 +02:00
9da3b77f7d modifications for Bootstrap 5
Updated Admin areas Users and Roles
2021-07-10 09:00:34 +02:00
7b796f4a5f Merge pull request #1548 from sbwalker/dev
modifications for Bootstrap 5
2021-07-09 11:38:42 -04:00
0ce81169a6 modifications for Bootstrap 5 2021-07-09 11:43:37 -04:00
d1c2abf7e3 Merge pull request #1545 from hishamco/fix
Fix loading satellite assemblies
2021-07-08 10:34:02 -04:00
010872ef35 Merge pull request #1546 from sbwalker/dev
bootstrap5 fix to theme creator template, modifications to updater app
2021-07-08 10:31:19 -04:00
f536033087 framework updater UX improvements 2021-07-08 10:36:08 -04:00
9083888686 bootstrap5 fix to theme creator template, modifications to updater app 2021-07-07 16:15:38 -04:00
cf2d8531a3 Fix loading satellite assemblies 2021-07-07 20:38:53 +03:00
48b9e2f97b Merge pull request #1538 from oqtane/master
Merge pull request #1537 from oqtane/dev
2021-07-06 15:31:55 -04:00
57ac20519a Merge pull request #1537 from oqtane/dev
2.2.0 release
2021-07-06 15:31:18 -04:00
187 changed files with 6503 additions and 4720 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

@ -12,11 +12,13 @@
{ {
@if (string.IsNullOrEmpty(_installation.Message)) @if (string.IsNullOrEmpty(_installation.Message))
{ {
<CascadingAuthenticationState> <div style="@_display">
<CascadingValue Value="@PageState"> <CascadingAuthenticationState>
<SiteRouter OnStateChange="@ChangeState" /> <CascadingValue Value="@PageState">
</CascadingValue> <SiteRouter Runtime="@Runtime" RenderMode="@RenderMode" OnStateChange="@ChangeState" />
</CascadingAuthenticationState> </CascadingValue>
</CascadingAuthenticationState>
</div>
} }
else else
{ {
@ -28,27 +30,54 @@
} }
@code { @code {
[Parameter]
public string AntiForgeryToken { get; set; }
[Parameter]
public string Runtime { get; set; }
[Parameter]
public string RenderMode { get; set; }
private bool _initialized = false; private bool _initialized = false;
private string _display = "display: none;";
private Installation _installation = new Installation { Success = false, Message = "" }; private Installation _installation = new Installation { Success = false, Message = "" };
private PageState PageState { get; set; } private PageState PageState { get; set; }
protected override async Task OnParametersSetAsync()
{
SiteState.AntiForgeryToken = AntiForgeryToken;
InstallationService.SetAntiForgeryTokenHeader(AntiForgeryToken);
_installation = await InstallationService.IsInstalled();
if (_installation.Alias != null)
{
SiteState.Alias = _installation.Alias;
}
else
{
_installation.Message = "Site Not Configured Correctly - No Matching Alias Exists For Host Name";
}
_initialized = true;
}
protected override async Task OnAfterRenderAsync(bool firstRender) protected override async Task OnAfterRenderAsync(bool firstRender)
{ {
if (firstRender && !_initialized) if (firstRender)
{ {
var interop = new Interop(JSRuntime); if (string.IsNullOrEmpty(AntiForgeryToken))
SiteState.AntiForgeryToken = await interop.GetElementByName(Constants.RequestVerificationToken);
_installation = await InstallationService.IsInstalled();
if (_installation.Alias != null)
{ {
SiteState.Alias = _installation.Alias; // 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);
SiteState.AntiForgeryToken = await interop.GetElementByName(Constants.RequestVerificationToken);
InstallationService.SetAntiForgeryTokenHeader(SiteState.AntiForgeryToken);
Runtime = await interop.GetElementByName("app_runtime");
RenderMode = await interop.GetElementByName("app_rendermode");
} }
else _display = "";
{
_installation.Message = "Site Not Configured Correctly - No Matching Alias Exists For Host Name";
}
_initialized = true;
StateHasChanged(); StateHasChanged();
} }
} }

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,89 +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
@inject IStringLocalizer<SharedResources> SharedLocalizer
@{ <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" /> </select>
</td> </div>
</tr> </div>
} @if (_security == "custom")
else {
{ <div class="row mb-1 align-items-center">
<tr> <Label Class="col-sm-3" For="uid" HelpText="Enter the username to use for the database" ResourceKey="Uid">User Id:</Label>
<td> <div class="col-sm-9">
<Label For="@fieldId" HelpText="@field.HelpText" ResourceKey="@field.Name">@Localizer[$"{field.FriendlyName}:"]</Label> <input id="uid" type="text" class="form-control" @bind="@_uid" />
</td> </div>
<td> </div>
<select id="@fieldId" class="custom-select" @bind="@field.Value"> <div class="row mb-1 align-items-center">
<option value="true" selected>@SharedLocalizer["True"]</option> <Label Class="col-sm-3" For="pwd" HelpText="Enter the password to use for the database" ResourceKey="Pwd">Password:</Label>
<option value="false">@SharedLocalizer["False"]</option> <div class="col-sm-9">
</select> <input id="pwd" type="password" class="form-control" @bind="@_pwd" />
</td> </div>
</tr> </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,87 +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
@inject IStringLocalizer<SharedResources> SharedLocalizer
@{ <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>
</select>
<tr style="@isVisible"> </div>
<td> </div>
<Label For="@fieldId" HelpText="@field.HelpText" ResourceKey="@field.Name">@Localizer[$"{field.FriendlyName}:"]</Label> @if (_security == "custom")
</td> {
<td> <div class="row mb-1 align-items-center">
<input id="@fieldId" type="@fieldType" class="form-control" @bind="@field.Value" /> <Label Class="col-sm-3" For="uid" HelpText="Enter the username to use for the database" ResourceKey="Uid">User Id:</Label>
</td> <div class="col-sm-9">
</tr> <input id="uid" type="text" class="form-control" @bind="@_uid" />
} </div>
else </div>
{ <div class="row mb-1 align-items-center">
<tr> <Label Class="col-sm-3" For="pwd" HelpText="Enter the password to use for the database" ResourceKey="Pwd">Password:</Label>
<td> <div class="col-sm-9">
<Label For="@fieldId" HelpText="@field.HelpText" ResourceKey="@field.Name">@Localizer[$"{field.FriendlyName}:"]</Label> <input id="pwd" type="password" class="form-control" @bind="@_pwd" />
</td> </div>
<td> </div>
<select id="@fieldId" class="custom-select" @bind="@field.Value">
<option value="true" selected>@SharedLocalizer["True"]</option>
<option value="false">@SharedLocalizer["False"]</option>
</select>
</td>
</tr>
}
}
} }
@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
{ {
@ -91,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

@ -20,78 +20,64 @@
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col text-center"> <div class="col text-center">
<h2>@Localizer["DatabaseConfig"]</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> @if (_databases != null)
<td> {
<select id="databasetype" class="custom-select" value="@_databaseName" @onchange="(e => DatabaseChanged(e))"> foreach (var database in _databases)
@if (_databases != null)
{ {
foreach (var database in _databases) if (database.IsDefault)
{ {
if (database.IsDefault) <option value="@database.Name" selected>@Localizer[@database.Name]</option>
{ }
<option value="@database.Name" selected>@Localizer[@database.Name]</option> else
} {
else <option value="@database.Name">@Localizer[@database.Name]</option>
{
<option value="@database.Name">@Localizer[@database.Name]</option>
}
} }
} }
</select> }
</td> </select>
</tr> </div>
@{ </div>
if (_databaseConfigType != null) @{
{ if (_databaseConfigType != null)
@DatabaseConfigComponent; {
} @DatabaseConfigComponent;
} }
</tbody> }
</table> </div>
</div> </div>
<div class="col text-center"> <div class="col text-center">
<h2>@Localizer["ApplicationAdmin"]</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> <input id="password" type="password" class="form-control" @bind="@_hostPassword" />
<td> </div>
<Label For="password" HelpText="Provide the password for the host user account" ResourceKey="Password">Password:</Label> </div>
</td> <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>
<input id="password" type="password" class="form-control" @bind="@_hostPassword" /> <div class="col-sm-9">
</td> <input id="confirm" type="password" class="form-control" @bind="@_confirmPassword" />
</tr> </div>
<tr> </div>
<td> <div class="row mb-1 align-items-center">
<Label For="confirm" HelpText="Please confirm the password entered above by entering it again" ResourceKey="Confirm">Confirm:</Label> <Label Class="col-sm-3" For="email" HelpText="Provide the email address for the host user account" ResourceKey="Email">Email:</Label>
</td> <div class="col-sm-9">
<td> <input type="text" class="form-control" @bind="@_hostEmail" />
<input id="confirm" type="password" class="form-control" @bind="@_confirmPassword" /> </div>
</td> </div>
</tr> </div>
<tr>
<td>
<Label For="email" HelpText="Provide the email address for the host user account" ResourceKey="Email">Email:</Label>
</td>
<td>
<input type="text" class="form-control" @bind="@_hostEmail" />
</td>
</tr>
</tbody>
</table>
</div> </div>
</div> </div>
<hr class="app-rule" /> <hr class="app-rule" />
@ -116,7 +102,7 @@
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;
@ -164,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", "");
} }
} }
@ -176,7 +163,7 @@
connectionString = databaseConfigControl.GetConnectionString(); connectionString = databaseConfigControl.GetConnectionString();
} }
if (connectionString != "" && _hostUsername != "" && _hostPassword.Length >= 6 && _hostPassword == _confirmPassword && _hostEmail != "" && _hostEmail.Contains("@")) if (connectionString != "" && !string.IsNullOrEmpty(_hostUsername) && _hostPassword.Length >= 6 && _hostPassword == _confirmPassword && !string.IsNullOrEmpty(_hostEmail) && _hostEmail.Contains("@"))
{ {
_loadingDisplay = ""; _loadingDisplay = "";
StateHasChanged(); StateHasChanged();
@ -190,9 +177,10 @@
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,
@ -215,5 +203,4 @@
_message = Localizer["Message.Require.DbInfo"]; _message = Localizer["Message.Require.DbInfo"];
} }
} }
} }

View File

@ -9,55 +9,60 @@
<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()">@SharedLocalizer["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> @foreach (Folder folder in _folders)
<select id="folder" class="form-select" @bind="@_folderId"> {
<option value="-1">&lt;@Localizer["Folder.Select"]&gt;</option> <option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option>
@foreach (Folder folder in _folders) }
{ </select>
<option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option> </div>
} </div>
</select> <div class="row mb-1 align-items-center">
</td> <Label Class="col-sm-3" For="name" HelpText="Enter the name of the file being downloaded" ResourceKey="Name">Name: </Label>
</tr> <div class="col-sm-9">
</table> <input id="name" class="form-control" @bind="@_name" />
<button type="button" class="btn btn-success" @onclick="Download">@SharedLocalizer["Download"]</button> </div>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink> </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;
@ -73,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["Message.Required.UrlFolder"], MessageType.Warning); if (_url == string.Empty || _folderId == -1)
return; {
} AddModuleMessage(Localizer["Message.Required.UrlFolder"], MessageType.Warning);
return;
}
var filename = url.Substring(url.LastIndexOf("/", StringComparison.Ordinal) + 1); if (string.IsNullOrEmpty(_name))
{
_name = _url.Substring(_url.LastIndexOf("/", StringComparison.Ordinal) + 1);
}
if (!Constants.UploadableFiles.Split(',') if (!Constants.UploadableFiles.Split(',').Contains(Path.GetExtension(_name).ToLower().Replace(".", "")))
.Contains(Path.GetExtension(filename).ToLower().Replace(".", ""))) {
{ AddModuleMessage(Localizer["Message.Download.InvalidExtension"], MessageType.Warning);
AddModuleMessage(Localizer["Message.Download.InvalidExtension"], MessageType.Warning); return;
return; }
}
if (!filename.IsPathOrFileValid()) if (!_name.IsPathOrFileValid())
{ {
AddModuleMessage(Localizer["Message.Required.UrlName"], 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["Success.Download.File"], MessageType.Success); AddModuleMessage(Localizer["Success.Download.File"], MessageType.Success);
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Downloading File From Url {Url} {Error}", _url, ex.Message);
AddModuleMessage(Localizer["Error.Download.InvalidUrl"], MessageType.Error);
}
} }
catch (Exception ex) else
{ {
await logger.LogError(ex, "Error Downloading File From Url {Url} {Error}", url, ex.Message); AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
AddModuleMessage(Localizer["Error.Download.InvalidUrl"], MessageType.Error);
} }
} }
} }

View File

@ -8,49 +8,54 @@
@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 width="30%"> <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> @foreach (Folder folder in _folders)
<td> {
<select id="parent" class="form-select" @bind="@_folderId"> <option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option>
@foreach (Folder folder in _folders) }
{ </select>
<option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option> </div>
} </div>
</select> <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>
</tr> <div class="col-sm-9">
<tr> <input id="description" class="form-control" @bind="@_description" />
<td> </div>
<Label for="size" HelpText="The size of the file (in bytes)" ResourceKey="Size">Size: </Label> </div>
</td> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="size" HelpText="The size of the file (in bytes)" ResourceKey="Size">Size: </Label>
<input id="size" class="form-control" @bind="@_size" readonly /> <div class="col-sm-9">
</td> <input id="size" class="form-control" @bind="@_size" readonly />
</tr> </div>
</table> </div>
<button type="button" class="btn btn-success" @onclick="SaveFile">@SharedLocalizer["Save"]</button> </div>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink> <button type="button" class="btn btn-success" @onclick="SaveFile">@SharedLocalizer["Save"]</button>
<br /> <NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
<br /> <br />
<AuditInfo CreatedBy="@_createdBy" CreatedOn="@_createdOn" ModifiedBy="@_modifiedBy" ModifiedOn="@_modifiedOn"></AuditInfo> <br />
<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;
@ -72,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;
@ -88,26 +94,36 @@
private async Task SaveFile() private async Task SaveFile()
{ {
try validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{ {
if (_name.IsPathOrFileValid()) try
{ {
File file = await FileService.GetFileAsync(_fileId); if (_name.IsPathOrFileValid())
file.Name = _name; {
file.FolderId = _folderId; File file = await FileService.GetFileAsync(_fileId);
file = await FileService.UpdateFileAsync(file); file.Name = _name;
await logger.LogInformation("File Saved {File}", file); file.FolderId = _folderId;
NavigationManager.NavigateTo(NavigateUrl()); file.Description = _description;
file = await FileService.UpdateFileAsync(file);
await logger.LogInformation("File Saved {File}", file);
NavigationManager.NavigateTo(NavigateUrl());
}
else
{
AddModuleMessage(Localizer["Message.File.InvalidName"], MessageType.Warning);
}
} }
else catch (Exception ex)
{ {
AddModuleMessage(Localizer["Message.File.InvalidName"], MessageType.Warning); await logger.LogError(ex, "Error Saving File {FileId} {Error}", _fileId, ex.Message);
AddModuleMessage(Localizer["Error.File.Save"], MessageType.Error);
} }
} }
catch (Exception ex) else
{ {
await logger.LogError(ex, "Error Saving File {FileId} {Error}", _fileId, ex.Message); AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
AddModuleMessage(Localizer["Error.File.Save"], MessageType.Error);
} }
} }
} }

View File

@ -8,57 +8,67 @@
@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 width="30%"> <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-select" @bind="@_parentId"> @if (PageState.QueryString.ContainsKey("id"))
{
<option value="-1">&lt;@Localizer["NoParent"]&gt;</option>
}
@foreach (Folder folder in _folders)
{
<option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option>
}
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="name" HelpText="Enter the folder name" ResourceKey="Name">Name: </Label>
<div class="col-sm-9">
<input id="name" class="form-control" @bind="@_name" maxlength="256" required />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="type" HelpText="Select the folder type. Private folders are only accessible by authorized users. Public folders can be accessed by all users" ResourceKey="Type">Type: </Label>
<div class="col-sm-9">
@if (PageState.QueryString.ContainsKey("id")) @if (PageState.QueryString.ContainsKey("id"))
{ {
<option value="-1">&lt;@Localizer["NoParent"]&gt;</option> <input id="type" class="form-control" readonly @bind="@_type" />
} }
@foreach (Folder folder in _folders) else
{ {
<option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option> <select id="type" class="form-select" @bind="@_type" required>
<option value="@FolderTypes.Private">@Localizer[FolderTypes.Private]</option>
<option value="@FolderTypes.Public">@Localizer[FolderTypes.Public]</option>
</select>
} }
</select> </div>
</td> </div>
</tr> <div class="row mb-1 align-items-center">
<tr> <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>
<td> <div class="col-sm-9">
<Label for="name" HelpText="Enter the folder name" ResourceKey="Name">Name: </Label> <input id="imagesizes" class="form-control" @bind="@_imagesizes" maxlength="512" />
</td> </div>
<td> </div>
<input id="name" class="form-control" @bind="@_name" /> <div class="row mb-1 align-items-center">
</td> <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>
</tr> <div class="col-sm-9">
<tr> <input id="capacity" class="form-control" @bind="@_capacity" required />
<td> </div>
<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> </div>
</td> <div class="row mb-1 align-items-center">
<td> <div class="col-sm-12">
@if (PageState.QueryString.ContainsKey("id")) <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" />
<input id="type" class="form-control" readonly @bind="@_type" />
} </div>
else </div>
{ </div>
<select id="type" class="form-select" @bind="@_type"> </form>
<option value="@FolderTypes.Private">@Localizer[FolderTypes.Private]</option>
<option value="@FolderTypes.Public">@Localizer[FolderTypes.Public]</option>
</select>
}
</td>
</tr>
<tr>
<td colspan="2" align="center">
<Label For="permissions" HelpText="Select the permissions you want for the folder" ResourceKey="Permissions">Permissions: </Label>
<PermissionGrid EntityName="@EntityNames.Folder" PermissionNames="@(PermissionNames.Browse + "," + PermissionNames.View + "," + PermissionNames.Edit)" Permissions="@_permissions" @ref="_permissionGrid" />
</td>
</tr>
</table>
@if (!_isSystem) @if (!_isSystem)
{ {
<button type="button" class="btn btn-success" @onclick="SaveFolder">@SharedLocalizer["Save"]</button> <button type="button" class="btn btn-success" @onclick="SaveFolder">@SharedLocalizer["Save"]</button>
@ -79,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;
@ -114,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;
@ -125,7 +141,6 @@
else else
{ {
_parentId = _folders[0].FolderId; _parentId = _folders[0].FolderId;
_permissions = string.Empty;
} }
} }
catch (Exception ex) catch (Exception ex)
@ -137,70 +152,81 @@
private async Task SaveFolder() private async Task SaveFolder()
{ {
if (_name == string.Empty || _parentId == -1) validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{ {
AddModuleMessage(Localizer["Message.Required.FolderParent"], MessageType.Warning); if (_name == string.Empty || _parentId == -1)
return;
}
if (!_name.IsPathOrFileValid())
{
AddModuleMessage(Localizer["Message.Folder.InvalidName"], MessageType.Warning);
return;
}
try
{
Folder folder;
if (_folderId != -1)
{ {
folder = await FolderService.GetFolderAsync(_folderId); AddModuleMessage(Localizer["Message.Required.FolderParent"], MessageType.Warning);
} return;
else
{
folder = new Folder();
} }
folder.SiteId = PageState.Site.SiteId; if (!_name.IsPathOrFileValid())
if (_parentId == -1)
{ {
folder.ParentId = null; AddModuleMessage(Localizer["Message.Folder.InvalidName"], MessageType.Warning);
} return;
else
{
folder.ParentId = _parentId;
} }
folder.Name = _name; try
folder.Type = _type; {
folder.IsSystem = _isSystem; Folder folder;
folder.Permissions = _permissionGrid.GetPermissions(); if (_folderId != -1)
{
folder = await FolderService.GetFolderAsync(_folderId);
}
else
{
folder = new Folder();
}
if (_folderId != -1) folder.SiteId = PageState.Site.SiteId;
{
folder = await FolderService.UpdateFolderAsync(folder);
}
else
{
folder = await FolderService.AddFolderAsync(folder);
}
if (folder != null) if (_parentId == -1)
{ {
await FolderService.UpdateFolderOrderAsync(folder.SiteId, folder.FolderId, folder.ParentId); folder.ParentId = null;
await logger.LogInformation("Folder Saved {Folder}", folder); }
NavigationManager.NavigateTo(NavigateUrl()); else
{
folder.ParentId = _parentId;
}
folder.Name = _name;
folder.Type = _type;
folder.ImageSizes = _imagesizes;
folder.Capacity = int.Parse(_capacity);
folder.IsSystem = _isSystem;
folder.Permissions = _permissionGrid.GetPermissions();
if (_folderId != -1)
{
folder = await FolderService.UpdateFolderAsync(folder);
}
else
{
folder = await FolderService.AddFolderAsync(folder);
}
if (folder != null)
{
await FolderService.UpdateFolderOrderAsync(folder.SiteId, folder.FolderId, folder.ParentId);
await logger.LogInformation("Folder Saved {Folder}", folder);
NavigationManager.NavigateTo(NavigateUrl());
}
else
{
AddModuleMessage(Localizer["Error.Folder.Save"], MessageType.Error);
}
} }
else catch (Exception ex)
{ {
await logger.LogError(ex, "Error Saving Folder {FolderId} {Error}", _folderId, ex.Message);
AddModuleMessage(Localizer["Error.Folder.Save"], MessageType.Error); AddModuleMessage(Localizer["Error.Folder.Save"], MessageType.Error);
} }
} }
catch (Exception ex) else
{ {
await logger.LogError(ex, "Error Saving Folder {FolderId} {Error}", _folderId, ex.Message); AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
AddModuleMessage(Localizer["Error.Folder.Save"], MessageType.Error);
} }
} }

View File

@ -8,34 +8,34 @@
@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-select" @onchange="(e => FolderChanged(e))"> <select class="form-select" @onchange="(e => FolderChanged(e))">
@foreach (Folder folder in _folders) @foreach (Folder folder in _folders)
{ {
<option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option> <option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option>
} }
</select> </select>
</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>@SharedLocalizer["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>

View File

@ -5,98 +5,111 @@
@inject IStringLocalizer<Edit> Localizer @inject IStringLocalizer<Edit> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer @inject IStringLocalizer<SharedResources> SharedLocalizer
<table class="table table-borderless"> <form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<tr> <div class="container">
<td width="30%"> <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> <input id="type" class="form-control" @bind="@_jobType" readonly />
</td> </div>
<td> </div>
<input id="type" class="form-control" @bind="@_jobType" readonly /> <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>
</tr> <div class="col-sm-9">
<tr> <select id="enabled" class="form-select" @bind="@_isEnabled" required>
<td> <option value="True">@SharedLocalizer["Yes"]</option>
<Label For="enabled" HelpText="Select whether you want the job enabled or not" ResourceKey="Enabled">Enabled? </Label> <option value="False">@SharedLocalizer["No"]</option>
</td> </select>
<td> </div>
<select id="enabled" class="form-select" @bind="@_isEnabled"> </div>
<option value="True">@SharedLocalizer["Yes"]</option> <div class="row mb-1 align-items-center">
<option value="False">@SharedLocalizer["No"]</option> <Label Class="col-sm-3" For="runs-every" HelpText="Select how often you want the job to run" ResourceKey="RunsEvery">Runs Every: </Label>
</select> <div class="col-sm-9">
</td> <input id="runs-every" class="form-control" @bind="@_interval" maxlength="4" required />
</tr> <select id="runs-every" class="form-select" @bind="@_frequency" required>
<tr> <option value="m">@Localizer["Minute(s)"]</option>
<td> <option value="H">@Localizer["Hour(s)"]</option>
<Label For="runs-every" HelpText="Select how often you want the job to run" ResourceKey="RunsEvery">Runs Every: </Label> <option value="d">@Localizer["Day(s)"]</option>
</td> <option value="M">@Localizer["Month(s)"]</option>
<td> </select>
<input id="runs-every" class="form-control" @bind="@_interval" /> </div>
<select id="runs-every" class="form-select" @bind="@_frequency"> </div>
<option value="m">@Localizer["Minute(s)"]</option> <div class="row mb-1 align-items-center">
<option value="H">@Localizer["Hour(s)"]</option> <Label Class="col-sm-3" For="retention" HelpText="Number of log entries to retain for this job" ResourceKey="RetentionLog">Retention Log (Items): </Label>
<option value="d">@Localizer["Day(s)"]</option> <div class="col-sm-9">
<option value="M">@Localizer["Month(s)"]</option> <input id="retention" class="form-control" @bind="@_retentionHistory" maxlength="4" required />
</select> </div>
</td> </div>
</tr> <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>
<td> <div class="col-sm-9">
<Label For="starting" HelpText="What time do you want the job to start" ResourceKey="Starting">Starting: </Label> <div class="row">
</td> <div class="col">
<td> <input id="starting" type="date" class="form-control" @bind="@_startDate" />
<input id="starting" class="form-control" @bind="@_startDate" /> </div>
</td> <div class="col">
</tr> <input id="starting" type="text" class="form-control" placeholder="hh:mm" @bind="@_startTime" />
<tr> </div>
<td> </div>
<Label For="ending" HelpText="When do you want the job to end" ResourceKey="Ending">Ending: </Label> </div>
</td> </div>
<td> <div class="row mb-1 align-items-center">
<input id="ending" class="form-control" @bind="@_endDate" /> <Label Class="col-sm-3" For="ending" HelpText="Optionally enter the date and time when this job should stop executing" ResourceKey="Ending">Ending: </Label>
</td> <div class="col-sm-9">
</tr> <div class="row">
<tr> <div class="col">
<td> <input id="ending" type="date" class="form-control" @bind="@_endDate" />
<Label For="retention" HelpText="Number of log entries to retain for this job" ResourceKey="RetentionLog">Retention Log (Items): </Label> </div>
</td> <div class="col">
<td> <input id="ending" type="text" class="form-control" placeholder="hh:mm" @bind="@_endTime" />
<input id="retention" class="form-control" @bind="@_retentionHistory" /> </div>
</td> </div>
</tr> </div>
<tr> </div>
<td> <div class="row mb-1 align-items-center">
<Label For="next" HelpText="Next execution for this job." ResourceKey="NextExecution">Next Execution: </Label> <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>
</td> <div class="col-sm-9">
<td> <div class="row">
<input id="next" class="form-control" @bind="@_nextExecution" /> <div class="col">
</td> <input id="next" type="date" class="form-control" @bind="@_nextDate" />
</tr> </div>
</table> <div class="col">
<button type="button" class="btn btn-success" @onclick="SaveJob">@SharedLocalizer["Save"]</button> <input id="next" type="text" class="form-control" placeholder="hh:mm" @bind="@_nextTime" />
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink> </div>
<br /> </div>
<br /> </div>
<AuditInfo CreatedBy="@createdby" CreatedOn="@createdon" ModifiedBy="@modifiedby" ModifiedOn="@modifiedon"></AuditInfo> </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 string createdby;
private DateTime createdon; private DateTime createdon;
private string modifiedby; private string modifiedby;
@ -117,10 +130,22 @@
_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; createdby = job.CreatedBy;
createdon = job.CreatedOn; createdon = job.CreatedOn;
modifiedby = job.ModifiedBy; modifiedby = job.ModifiedBy;
@ -136,7 +161,9 @@
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;
@ -144,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))
{
job.StartDate = DateTime.Parse(job.StartDate.Value.ToShortDateString() + " " + _startTime);
}
} }
else job.EndDate = _endDate;
if (job.EndDate != null)
{ {
job.StartDate = DateTime.Parse(_startDate); job.EndDate = job.EndDate.Value.Date;
if (!string.IsNullOrEmpty(_endTime))
{
job.EndDate = DateTime.Parse(job.EndDate.Value.ToShortDateString() + " " + _endTime);
}
} }
if (_endDate == string.Empty)
{
job.EndDate = null;
}
else
{
job.EndDate = DateTime.Parse(_endDate);
}
if (_nextExecution == string.Empty)
{
job.NextExecution = null;
}
else
{
job.NextExecution = DateTime.Parse(_nextExecution);
}
job.RetentionHistory = int.Parse(_retentionHistory); job.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
{ {
@ -191,5 +217,4 @@
AddModuleMessage(Localizer["Message.Required.JobInfo"], MessageType.Warning); AddModuleMessage(Localizer["Message.Required.JobInfo"], MessageType.Warning);
} }
} }
} }

View File

@ -23,50 +23,48 @@ else
} }
else else
{ {
<table class="table table-borderless"> <form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<tr> <div class="container">
<td width="30%"> <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-select" @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> </div>
</td> </div>
</tr> <div class="row mb-1 align-items-center">
<tr> <Label Class="col-sm-3" For="default" HelpText="Indicates Whether Or Not This Language Is The Default For The Site" ResourceKey="IsDefault">Default?</Label>
<td> <div class="col-sm-9">
<Label For="default" HelpText="Indicates Whether Or Not This Language Is The Default For The Site" ResourceKey="IsDefault">Default?</Label> <select id="default" class="form-select" @bind="@_isDefault" required>
</td> <option value="True">@SharedLocalizer["Yes"]</option>
<td> <option value="False">@SharedLocalizer["No"]</option>
<select id="default" class="form-select" @bind="@_isDefault"> </select>
<option value="True">@SharedLocalizer["Yes"]</option> </div>
<option value="False">@SharedLocalizer["No"]</option> </div>
</select> </div>
</td> <button type="button" class="btn btn-success" @onclick="SaveLanguage">@SharedLocalizer["Save"]</button>
</tr> </form>
</table>
<button type="button" class="btn btn-success" @onclick="SaveLanguage">@SharedLocalizer["Save"]</button>
} }
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink> <NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
</TabPanel> </TabPanel>
<TabPanel Name="Download" ResourceKey="Download" Security="SecurityAccessLevel.Host"> <TabPanel Name="Download" ResourceKey="Download" Security="SecurityAccessLevel.Host">
<ModuleMessage Type="MessageType.Info" Message="Download one or more translations from the list below. Once you are ready click Install to complete the installation."></ModuleMessage> <div class="row justify-content-center mb-3">
<div class="col-sm-6">
<table class="table table-borderless" style=" margin: auto; width: 50% !important;"> <div class="input-group">
<tr> <select id="price" class="form-select custom-select" @onchange="(e => PriceChanged(e))">
<td> <option value="free">@SharedLocalizer["Free"]</option>
<option value="paid">@SharedLocalizer["Paid"]</option>
</select>
<input id="search" class="form-control" placeholder="@SharedLocalizer["Search.Hint"]" @bind="@_search" /> <input id="search" class="form-control" placeholder="@SharedLocalizer["Search.Hint"]" @bind="@_search" />
</td> <button type="button" class="btn btn-primary" @onclick="Search">@SharedLocalizer["Search"]</button>
<td>
<button type="button" class="btn btn-primary" @onclick="Search">@SharedLocalizer["Search"]</button>&nbsp;
<button type="button" class="btn btn-secondary" @onclick="Reset">@SharedLocalizer["Reset"]</button> <button type="button" class="btn btn-secondary" @onclick="Reset">@SharedLocalizer["Reset"]</button>
</td> </div>
</tr> </div>
</table> </div>
@if (_packages != null) @if (_packages != null)
{ {
@ -77,10 +75,26 @@ else
<td> <td>
<h3 style="display: inline;"><a href="@context.ProductUrl" target="_new">@context.Name</a></h3>&nbsp;&nbsp;by:&nbsp;&nbsp;<strong><a href="@context.OwnerUrl" target="new">@context.Owner</a></strong><br /> <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 /> @(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>&nbsp;&nbsp;|&nbsp;&nbsp;@SharedLocalizer["Search.Source"]: <strong>@context.PackageUrl</strong> <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>
<td style="vertical-align: middle;"> <td style="width: 1px; vertical-align: middle;">
<button type="button" class="btn btn-primary" @onclick=@(async () => await DownloadLanguage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button> @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>
@ -97,30 +111,69 @@ else
} }
</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 translations. 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>
</table>
<button type="button" class="btn btn-success" @onclick="InstallLanguages">@SharedLocalizer["Install"]</button> <button type="button" class="btn btn-success" @onclick="InstallLanguages">@SharedLocalizer["Install"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink> <NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
</TabPanel> </TabPanel>
</TabStrip> </TabStrip>
} }
@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 _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;
@ -146,7 +199,22 @@ else
private async Task LoadTranslations() private async Task LoadTranslations()
{ {
_packages = await PackageService.GetPackagesAsync("translation", _search); _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() private async Task Search()
@ -176,31 +244,88 @@ else
private async Task SaveLanguage() private async Task SaveLanguage()
{ {
var language = new Language validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{ {
SiteId = PageState.Page.SiteId, var language = new Language
Name = CultureInfo.GetCultureInfo(_code).DisplayName, {
Code = _code, SiteId = PageState.Page.SiteId,
IsDefault = (_isDefault == null ? false : Boolean.Parse(_isDefault)) Name = CultureInfo.GetCultureInfo(_code).DisplayName,
}; Code = _code,
IsDefault = (_isDefault == null ? false : Boolean.Parse(_isDefault))
};
try
{
language = await LanguageService.AddLanguageAsync(language);
if (language.IsDefault)
{
await SetCultureAsync(language.Code);
}
await logger.LogInformation("Language Added {Language}", language);
NavigationManager.NavigateTo(NavigateUrl());
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Adding Language {Language} {Error}", language, ex.Message);
AddModuleMessage(Localizer["Error.Language.Add"], MessageType.Error);
}
}
else
{
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
}
}
private void HideModal()
{
_productname = "";
_license = "";
StateHasChanged();
}
private async Task GetPackage(string packageid, string version)
{
try try
{ {
language = await LanguageService.AddLanguageAsync(language); var package = await PackageService.GetPackageAsync(packageid, version);
if (package != null)
if (language.IsDefault)
{ {
await SetCultureAsync(language.Code); _productname = package.Name;
if (!string.IsNullOrEmpty(package.License))
{
_license = package.License.Replace("\n", "<br />");
}
_packageid = package.PackageId;
_version = package.Version;
} }
StateHasChanged();
await logger.LogInformation("Language Added {Language}", language);
NavigationManager.NavigateTo(NavigateUrl());
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Adding Language {Language} {Error}", language, ex.Message); await logger.LogError(ex, "Error Getting Package {PackageId} {Version}", packageid, version);
AddModuleMessage(Localizer["Error.Language.Add"], MessageType.Error); 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);
} }
} }
@ -217,22 +342,6 @@ else
} }
} }
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["Success.Language.Download"], MessageType.Success);
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Downloading Translation {Name} {Version}", packageid, version);
AddModuleMessage(Localizer["Error.Language.Download"], MessageType.Error);
}
}
private async Task SetCultureAsync(string culture) private async Task SetCultureAsync(string culture)
{ {
if (culture != CultureInfo.CurrentUICulture.Name) if (culture != CultureInfo.CurrentUICulture.Name)

View File

@ -29,9 +29,9 @@ else
<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))>@SharedLocalizer["Upgrade"]</button> <button type="button" class="btn btn-success" @onclick=@(async () => await DownloadLanguage(context.Code))>@SharedLocalizer["Upgrade"]</button>
} }
</td> </td>
</Row> </Row>
</Pager> </Pager>

View File

@ -59,7 +59,7 @@
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous;
public override List<Resource> Resources => new List<Resource>() public override List<Resource> Resources => new List<Resource>()
{ {
new Resource { ResourceType = ResourceType.Stylesheet, Url = ModulePath() + "Module.css" } new Resource { ResourceType = ResourceType.Stylesheet, Url = ModulePath() + "Module.css" }
}; };
@ -84,10 +84,12 @@
if (user != null) if (user != null)
{ {
await logger.LogInformation(LogFunction.Security, "Email Verified For For Username {Username}", _username);
_message = Localizer["Success.Account.Verified"]; _message = Localizer["Success.Account.Verified"];
} }
else else
{ {
await logger.LogError(LogFunction.Security, "Email Verification Failed For Username {Username}", _username);
_message = Localizer["Message.Account.NotVerfied"]; _message = Localizer["Message.Account.NotVerfied"];
_type = MessageType.Warning; _type = MessageType.Warning;
} }
@ -98,7 +100,10 @@
{ {
if (firstRender) if (firstRender)
{ {
await username.FocusAsync(); if(PageState.User == null)
{
await username.FocusAsync();
}
} }
} }
@ -118,7 +123,7 @@
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);
// server-side Blazor needs to post to the Login page so that the cookies are set correctly // server-side Blazor needs to post to the Login page so that the cookies are set correctly
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, username = _username, password = _password, remember = _remember, returnurl = _returnUrl }; var fields = new { __RequestVerificationToken = SiteState.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/");
@ -126,7 +131,7 @@
} }
else else
{ {
await logger.LogInformation("Login Failed For Username {Username}", _username); await logger.LogError(LogFunction.Security, "Login Failed For Username {Username}", _username);
AddModuleMessage(Localizer["Error.Login.Fail"], MessageType.Error); AddModuleMessage(Localizer["Error.Login.Fail"], MessageType.Error);
} }
} }
@ -140,14 +145,14 @@
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, true)); 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["Error.Login.Fail"], MessageType.Error); AddModuleMessage(Localizer["Error.Login.Fail"], MessageType.Error);
} }
} }
@ -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

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

View File

@ -10,9 +10,9 @@
} }
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-select" @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>
@ -23,8 +23,8 @@ 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-select" @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>
@ -35,27 +35,27 @@ else
<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-select" @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())
{ {
<Pager Items="@_logs"> <Pager Items="@_logs">
<Header> <Header>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th>@Localizer["Date"]</th> <th>@Localizer["Date"]</th>
<th>@Localizer["Level"]</th> <th>@Localizer["Level"]</th>
<th>@Localizer["Feature"]</th> <th>@Localizer["Feature"]</th>
<th>@Localizer["Function"]</th> <th>@Localizer["Function"]</th>
</Header> </Header>
<Row> <Row>
<td class="@GetClass(context.Function)"><ActionLink Action="Detail" Parameters="@($"id=" + context.LogId.ToString())" ResourceKey="LogDetails" /></td> <td class="@GetClass(context.Function)"><ActionLink Action="Detail" Parameters="@($"id=" + context.LogId.ToString())" ResourceKey="LogDetails" /></td>

View File

@ -10,82 +10,75 @@
@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 width="30%"> <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" ></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> @foreach (Template template in _templates)
<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-select" @onchange="(e => TemplateChanged(e))">
<option value="-">&lt;@Localizer["Template.Select"]&gt;</option>
@foreach (Template template in _templates)
{
<option value="@template.Name">@template.Title</option>
}
</select>
</td>
</tr>
<tr>
<td>
<Label For="reference" HelpText="Select a framework reference version" ResourceKey="FrameworkReference">Framework Reference: </Label>
</td>
<td>
<select id="reference" class="form-select" @bind="@_reference">
@foreach (string version in _versions)
{
if (Version.Parse(version).CompareTo(Version.Parse(_minversion)) >= 0)
{ {
<option value="@(version)">@(version)</option> <option value="@template.Name">@template.Title</option>
} }
} </select>
<option value="local">@SharedLocalizer["LocalVersion"]</option> </div>
</select> </div>
</td> <div class="row mb-1 align-items-center">
</tr> <Label Class="col-sm-3" For="reference" HelpText="Select a framework reference version" ResourceKey="FrameworkReference">Framework Reference: </Label>
@if (!string.IsNullOrEmpty(_location)) <div class="col-sm-9">
{ <select id="reference" class="form-select" @bind="@_reference" required>
<tr> @foreach (string version in _versions)
<td> {
<Label For="location" HelpText="Location where the module will be created" ResourceKey="Location">Location: </Label> if (Version.Parse(version).CompareTo(Version.Parse(_minversion)) >= 0)
</td> {
<td> <option value="@(version)">@(version)</option>
<input id="module" class="form-control" @bind="@_location" readonly /> }
</td> }
</tr> <option value="local">@SharedLocalizer["LocalVersion"]</option>
</select>
</div>
</div>
@if (!string.IsNullOrEmpty(_location))
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="location" HelpText="Location where the module will be created" ResourceKey="Location">Location: </Label>
<div class="col-sm-9">
<input id="module" class="form-control" @bind="@_location" readonly />
</div>
</div>
}
</div>
<button type="button" class="btn btn-success" @onclick="CreateModule">@Localizer["Module.Create"]</button>
} }
</table> else
<button type="button" class="btn btn-success" @onclick="CreateModule">@Localizer["Module.Create"]</button> {
} <button type="button" class="btn btn-success" @onclick="ActivateModule">@Localizer["Module.Activate"]</button>
else </form>
{
<button type="button" class="btn btn-success" @onclick="ActivateModule">@Localizer["Module.Activate"]</button>
} }
@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;
@ -124,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);
@ -139,14 +134,14 @@ else
AddModuleMessage(string.Format(Localizer["Success.Module.Create"], NavigateUrl("admin/system")), MessageType.Success); AddModuleMessage(string.Format(Localizer["Success.Module.Create"], NavigateUrl("admin/system")), MessageType.Success);
} }
else catch (Exception ex)
{ {
AddModuleMessage(Localizer["Message.Require.ValidName"], MessageType.Warning); await logger.LogError(ex, "Error Creating Module");
} }
} }
catch (Exception ex) else
{ {
await logger.LogError(ex, "Error Creating Module"); AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
} }
} }

View File

@ -9,19 +9,19 @@
<TabStrip> <TabStrip>
<TabPanel Name="Download" ResourceKey="Download"> <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> <div class="row justify-content-center mb-3">
<div class="col-sm-6">
<table class="table table-borderless" style="margin: auto; width: 50% !important;"> <div class="input-group">
<tr> <select id="price" class="form-select custom-select" @onchange="(e => PriceChanged(e))">
<td> <option value="free">@SharedLocalizer["Free"]</option>
<option value="paid">@SharedLocalizer["Paid"]</option>
</select>
<input id="search" class="form-control" placeholder="@SharedLocalizer["Search.Hint"]" @bind="@_search" /> <input id="search" class="form-control" placeholder="@SharedLocalizer["Search.Hint"]" @bind="@_search" />
</td> <button type="button" class="btn btn-primary" @onclick="Search">@SharedLocalizer["Search"]</button>
<td>
<button type="button" class="btn btn-primary" @onclick="Search">@SharedLocalizer["Search"]</button>&nbsp;
<button type="button" class="btn btn-secondary" @onclick="Reset">@SharedLocalizer["Reset"]</button> <button type="button" class="btn btn-secondary" @onclick="Reset">@SharedLocalizer["Reset"]</button>
</td> </div>
</tr> </div>
</table> </div>
@if (_packages != null) @if (_packages != null)
{ {
@ -32,10 +32,26 @@
<td> <td>
<h3 style="display: inline;"><a href="@context.ProductUrl" target="_new">@context.Name</a></h3>&nbsp;&nbsp;by:&nbsp;&nbsp;<strong><a href="@context.OwnerUrl" target="new">@context.Owner</a></strong><br /> <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 /> @(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>&nbsp;&nbsp;|&nbsp;&nbsp;@SharedLocalizer["Search.Source"]: <strong>@context.PackageUrl</strong> <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>
<td style="vertical-align: middle;"> <td style="width: 1px; vertical-align: middle;">
<button type="button" class="btn btn-primary" @onclick=@(async () => await DownloadModule(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button> @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>
@ -50,25 +66,61 @@
} }
</TabPanel> </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>
@if (_productname != "")
{
<div class="app-actiondialog">
<div class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">@SharedLocalizer["Review License Terms"]</h5>
<button type="button" class="btn-close" aria-label="Close" @onclick="HideModal"></button>
</div>
<div class="modal-body">
<p style="height: 200px; overflow-y: scroll;">
<h3>@_productname</h3>
@if (!string.IsNullOrEmpty(_license))
{
@((MarkupString)_license)
}
else
{
@SharedLocalizer["License Not Specified"]
}
</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-success" @onclick="DownloadPackage">@SharedLocalizer["Accept"]</button>
<button type="button" class="btn btn-secondary" @onclick="HideModal">@SharedLocalizer["Cancel"]</button>
</div>
</div>
</div>
</div>
</div>
}
<button type="button" class="btn btn-success" @onclick="InstallModules">@SharedLocalizer["Install"]</button> <button type="button" class="btn btn-success" @onclick="InstallModules">@SharedLocalizer["Install"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink> <NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
@code { @code {
private List<Package> _packages; private List<Package> _packages;
private string _price = "free";
private string _search = ""; 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;
@ -88,7 +140,7 @@
private async Task LoadModuleDefinitions() private async Task LoadModuleDefinitions()
{ {
var moduledefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId); var moduledefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Site.SiteId);
_packages = await PackageService.GetPackagesAsync("module", _search); _packages = await PackageService.GetPackagesAsync("module", _search, _price, "");
if (_packages != null) if (_packages != null)
{ {
@ -102,6 +154,21 @@
} }
} }
private async void PriceChanged(ChangeEventArgs e)
{
try
{
_price = (string)e.Value;
_search = "";
await LoadModuleDefinitions();
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error On PriceChanged");
}
}
private async Task Search() private async Task Search()
{ {
try try
@ -127,6 +194,55 @@
} }
} }
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);
}
}
private async Task InstallModules() private async Task InstallModules()
{ {
try try
@ -139,20 +255,4 @@
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["Success.Module.Download"], MessageType.Success);
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Downloading Module {ModuleDefinitionName} {Version}", packageid, version);
AddModuleMessage(Localizer["Error.Module.Download"], MessageType.Error);
}
}
} }

View File

@ -10,79 +10,71 @@
@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 width="30%"> <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> @foreach (Template template in _templates)
<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-select" @onchange="(e => TemplateChanged(e))">
<option value="-">&lt;@Localizer["Template.Select"]&gt;</option>
@foreach (Template template in _templates)
{
<option value="@template.Name">@template.Title</option>
}
</select>
</td>
</tr>
<tr>
<td>
<Label For="reference" HelpText="Select a framework reference version" ResourceKey="FrameworkReference">Framework Reference: </Label>
</td>
<td>
<select id="reference" class="form-select" @bind="@_reference">
@foreach (string version in _versions)
{
if (Version.Parse(version).CompareTo(Version.Parse(_minversion)) >= 0)
{ {
<option value="@(version)">@(version)</option> <option value="@template.Name">@template.Title</option>
} }
} </select>
<option value="local">@SharedLocalizer["LocalVersion"]</option> </div>
</select> </div>
</td> <div class="row mb-1 align-items-center">
</tr> <Label Class="col-sm-3" For="reference" HelpText="Select a framework reference version" ResourceKey="FrameworkReference">Framework Reference: </Label>
@if (!string.IsNullOrEmpty(_location)) <div class="col-sm-9">
{ <select id="reference" class="form-select" @bind="@_reference" required>
<tr> @foreach (string version in _versions)
<td> {
<Label For="location" HelpText="Location where the module will be created" ResourceKey="Location">Location: </Label> if (Version.Parse(version).CompareTo(Version.Parse(_minversion)) >= 0)
</td> {
<td> <option value="@(version)">@(version)</option>
<input id="module" class="form-control" @bind="@_location" readonly /> }
</td> }
</tr> <option value="local">@SharedLocalizer["LocalVersion"]</option>
} </select>
</table> </div>
<button type="button" class="btn btn-success" @onclick="CreateModule">@Localizer["CreateModule"]</button> </div>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink> @if (!string.IsNullOrEmpty(_location))
{
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="location" HelpText="Location where the module will be created" ResourceKey="Location">Location: </Label>
<div class="col-sm-9">
<input id="module" class="form-control" @bind="@_location" readonly />
</div>
</div>
}
</div>
<button type="button" class="btn btn-success" @onclick="CreateModule">@Localizer["CreateModule"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
</form>
} }
@code { @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;
@ -111,23 +103,32 @@
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 }; if (IsValid(_owner) && IsValid(_module) && _owner != _module && _template != "-")
moduleDefinition = await ModuleDefinitionService.CreateModuleDefinitionAsync(moduleDefinition); {
GetLocation(); var moduleDefinition = new ModuleDefinition { Owner = _owner, Name = _module, Description = _description, Template = _template, Version = _reference };
AddModuleMessage(string.Format(Localizer["Success.Module.Create"], NavigateUrl("admin/system")), MessageType.Success); moduleDefinition = await ModuleDefinitionService.CreateModuleDefinitionAsync(moduleDefinition);
GetLocation();
AddModuleMessage(string.Format(Localizer["Success.Module.Create"], NavigateUrl("admin/system")), MessageType.Success);
}
else
{
AddModuleMessage(Localizer["Message.Require.ValidName"], MessageType.Warning);
}
} }
else catch (Exception ex)
{ {
AddModuleMessage(Localizer["Message.Require.ValidName"], MessageType.Warning); await logger.LogError(ex, "Error Creating Module");
} }
} }
catch (Exception ex) else
{ {
await logger.LogError(ex, "Error Creating Module"); AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
} }
} }

View File

@ -7,101 +7,81 @@
<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 width="30%"> <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 width="30%"> <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" /> </div>
</td> </div>
</tr>
</table>
</TabPanel> </TabPanel>
</TabStrip> </TabStrip>
<button type="button" class="btn btn-success" @onclick="SaveModuleDefinition">@SharedLocalizer["Save"]</button> <button type="button" class="btn btn-success" @onclick="SaveModuleDefinition">@SharedLocalizer["Save"]</button>
@ -111,6 +91,8 @@
<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;
@ -168,30 +150,39 @@
private async Task SaveModuleDefinition() private async Task SaveModuleDefinition()
{ {
try validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{ {
var moduledefinition = await ModuleDefinitionService.GetModuleDefinitionAsync(_moduleDefinitionId, ModuleState.SiteId); try
if (moduledefinition.Name != _name)
{ {
moduledefinition.Name = _name; var moduledefinition = await ModuleDefinitionService.GetModuleDefinitionAsync(_moduleDefinitionId, ModuleState.SiteId);
if (moduledefinition.Name != _name)
{
moduledefinition.Name = _name;
}
if (moduledefinition.Description != _description)
{
moduledefinition.Description = _description;
}
if (moduledefinition.Categories != _categories)
{
moduledefinition.Categories = _categories;
}
moduledefinition.Permissions = _permissionGrid.GetPermissions();
await ModuleDefinitionService.UpdateModuleDefinitionAsync(moduledefinition);
await logger.LogInformation("ModuleDefinition Saved {ModuleDefinition}", moduledefinition);
NavigationManager.NavigateTo(NavigateUrl());
} }
if (moduledefinition.Description != _description) catch (Exception ex)
{ {
moduledefinition.Description = _description; await logger.LogError(ex, "Error Saving ModuleDefinition {ModuleDefinitionId} {Error}", _moduleDefinitionId, ex.Message);
AddModuleMessage(Localizer["Error.Module.Save"], MessageType.Error);
} }
if (moduledefinition.Categories != _categories)
{
moduledefinition.Categories = _categories;
}
moduledefinition.Permissions = _permissionGrid.GetPermissions();
await ModuleDefinitionService.UpdateModuleDefinitionAsync(moduledefinition);
await logger.LogInformation("ModuleDefinition Saved {ModuleDefinition}", moduledefinition);
NavigationManager.NavigateTo(NavigateUrl());
} }
catch (Exception ex) else
{ {
await logger.LogError(ex, "Error Saving ModuleDefinition {ModuleDefinitionId} {Error}", _moduleDefinitionId, ex.Message); AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
AddModuleMessage(Localizer["Error.Module.Save"], MessageType.Error);
} }
} }
} }

View File

@ -22,6 +22,7 @@ else
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th>@SharedLocalizer["Name"]</th> <th>@SharedLocalizer["Name"]</th>
<th>@SharedLocalizer["Version"]</th> <th>@SharedLocalizer["Version"]</th>
<th>@SharedLocalizer["Expires"]</th>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
</Header> </Header>
<Row> <Row>
@ -34,11 +35,14 @@ else
</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))>@SharedLocalizer["Upgrade"]</button> <button type="button" class="btn btn-success" @onclick=@(async () => await DownloadModule(context.PackageName, context.Version))>@SharedLocalizer["Upgrade"]</button>
} }
</td> </td>
</Row> </Row>
</Pager> </Pager>
@ -67,10 +71,31 @@ else
} }
} }
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)

View File

@ -5,31 +5,35 @@
@inject IStringLocalizer<Export> Localizer @inject IStringLocalizer<Export> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer @inject IStringLocalizer<SharedResources> SharedLocalizer
<table class="table table-borderless"> <div class="container">
<tbody> <div class="row mb-1 align-items-center">
<tr> <Label Class="col-sm-3" For="content" HelpText="The Exported Module Content" ResourceKey="Content">Content: </Label>
<td width="30%"> <div class="col-sm-9">
<Label For="content" HelpText="Enter the module content" ResourceKey="Content">Content: </Label> <textarea id="content" class="form-control" @bind="@_content" rows="5" readonly></textarea>
</td> </div>
<td> </div>
<textarea id="content" class="form-control" @bind="@_content" rows="5"></textarea> </div>
</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()">@SharedLocalizer["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()
{ {
_content = await ModuleService.ExportModuleAsync(ModuleState.ModuleId); try
{
_content = await ModuleService.ExportModuleAsync(ModuleState.ModuleId);
AddModuleMessage(Localizer["Success.Content.Export"], MessageType.Success);
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Exporting Module {ModuleId} {Error}", ModuleState.ModuleId, ex.Message);
AddModuleMessage(Localizer["Error.Module.Export"], MessageType.Error);
}
} }
} }

View File

@ -5,53 +5,63 @@
@inject IStringLocalizer<Import> Localizer @inject IStringLocalizer<Import> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer @inject IStringLocalizer<SharedResources> SharedLocalizer
<table class="table table-borderless"> <form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<tbody> <div class="container">
<tr> <div class="row mb-1 align-items-center">
<td width="30%"> <Label Class="col-sm-3" For="content" HelpText="Enter The Module Content To Import" ResourceKey="Content">Content: </Label>
<Label For="content" HelpText="Enter the module content" ResourceKey="Content">Content: </Label> <div class="col-sm-9">
</td> <textarea id="content" class="form-control" @bind="@_content" rows="5" required></textarea>
<td>
<textarea id="content" class="form-control" @bind="@_content" rows="5"></textarea>
</td>
</tr>
</tbody>
</table>
<button type="button" class="btn btn-success" @onclick="ImportModule">@Localizer["Import"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
</div>
</div>
</div>
<button type="button" class="btn btn-success" @onclick="ImportModule">@Localizer["Import"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
</form>
@code { @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()
{ {
if (_content != string.Empty) validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{ {
try if (_content != string.Empty)
{ {
bool success = await ModuleService.ImportModuleAsync(ModuleState.ModuleId, _content); try
if (success)
{ {
AddModuleMessage(Localizer["Success.Content.Import"], MessageType.Success); bool success = await ModuleService.ImportModuleAsync(ModuleState.ModuleId, _content);
if (success)
{
AddModuleMessage(Localizer["Success.Content.Import"], MessageType.Success);
}
else
{
AddModuleMessage(Localizer["Message.Content.ImportProblem"], MessageType.Warning);
}
} }
else catch (Exception ex)
{ {
AddModuleMessage(Localizer["Message.Content.ImportProblem"], MessageType.Warning); await logger.LogError(ex, "Error Importing Module {ModuleId} {Error}", ModuleState.ModuleId, ex.Message);
AddModuleMessage(Localizer["Error.Module.Import"], MessageType.Error);
} }
} }
catch (Exception ex) else
{ {
await logger.LogError(ex, "Error Importing Module {ModuleId} {Error}", ModuleState.ModuleId, ex.Message); AddModuleMessage(Localizer["Message.Required.ImportContent"], MessageType.Warning);
AddModuleMessage(Localizer["Error.Module.Import"], MessageType.Error);
} }
} }
else else
{ {
AddModuleMessage(Localizer["Message.Required.ImportContent"], MessageType.Warning); AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
} }
} }
} }

View File

@ -8,97 +8,93 @@
@inject IStringLocalizer<Settings> Localizer @inject IStringLocalizer<Settings> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer @inject IStringLocalizer<SharedResources> SharedLocalizer
<TabStrip> <form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings"> <TabStrip>
@if (_containers != null) <TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings">
{ @if (_containers != null)
<table class="table table-borderless"> {
<tr> <div class="container">
<td width="30%"> <div class="row mb-1 align-items-center">
<Label For="title" HelpText="Enter the title of the module" ResourceKey="Title">Title: </Label> <Label Class="col-sm-3" For="title" HelpText="Enter the title of the module" ResourceKey="Title">Title: </Label>
</td> <div class="col-sm-9">
<td> <input id="title" type="text" name="Title" class="form-control" @bind="@_title" required />
<input id="title" type="text" name="Title" class="form-control" @bind="@_title" /> </div>
</td> </div>
</tr> <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>
<td> <div class="col-sm-9">
<Label For="container" HelpText="Select the module's container" ResourceKey="Container">Container: </Label> <select id="container" class="form-select" @bind="@_containerType" required>
</td> @foreach (var container in _containers)
<td>
<select id="container" class="form-select" @bind="@_containerType">
@foreach (var container in _containers)
{
<option value="@container.TypeName">@container.Name</option>
}
</select>
</td>
</tr>
<tr>
<td>
<Label For="allpages" HelpText="Indicate if this module should be displayed on all pages" ResourceKey="DisplayOnAllPages">Display On All Pages? </Label>
</td>
<td>
<select id="allpages" class="form-select" @bind="@_allPages">
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</td>
</tr>
<tr>
<td>
<Label For="page" HelpText="The page that the module is located on" ResourceKey="Page">Page: </Label>
</td>
<td>
<select id="page" class="form-select" @bind="@_pageId">
@foreach (Page p in PageState.Pages)
{
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.Permissions))
{ {
<option value="@p.PageId">@(new string('-', p.Level * 2))@(p.Name)</option> <option value="@container.TypeName">@container.Name</option>
} }
} </select>
</select> </div>
</td> </div>
</tr> <div class="row mb-1 align-items-center">
</table> <Label Class="col-sm-3" For="allpages" HelpText="Indicate if this module should be displayed on all pages" ResourceKey="DisplayOnAllPages">Display On All Pages? </Label>
} <div class="col-sm-9">
</TabPanel> <select id="allpages" class="form-select" @bind="@_allPages" required>
<TabPanel Name="Permissions" ResourceKey="Permissions"> <option value="True">@SharedLocalizer["Yes"]</option>
@if (_permissions != null) <option value="False">@SharedLocalizer["No"]</option>
{ </select>
<table class="table table-borderless"> </div>
<tr> </div>
<td> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="page" HelpText="The page that the module is located on" ResourceKey="Page">Page: </Label>
<div class="col-sm-9">
<select id="page" class="form-select" @bind="@_pageId" required>
@foreach (Page p in PageState.Pages)
{
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.Permissions))
{
<option value="@p.PageId">@(new string('-', p.Level * 2))@(p.Name)</option>
}
}
</select>
</div>
</div>
</div>
}
</TabPanel>
<TabPanel Name="Permissions" ResourceKey="Permissions">
@if (_permissions != null)
{
<div class="container">
<div class="row mb-1 align-items-center">
<PermissionGrid EntityName="@EntityNames.Module" PermissionNames="@_permissionNames" Permissions="@_permissions" @ref="_permissionGrid" /> <PermissionGrid EntityName="@EntityNames.Module" PermissionNames="@_permissionNames" Permissions="@_permissions" @ref="_permissionGrid" />
</td> </div>
</tr> </div>
</table>
}
</TabPanel>
@if (_moduleSettingsType != null)
{
<TabPanel Name="ModuleSettings" Heading="@_moduleSettingsTitle" ResourceKey="ModuleSettings">
@ModuleSettingsComponent
</TabPanel>
} }
</TabPanel> @if (_containerSettingsType != null)
@if (_moduleSettingsType != null) {
{ <TabPanel Name="ContainerSettings" Heading="Container Settings" ResourceKey="ContainerSettings">
<TabPanel Name="ModuleSettings" Heading="@_moduleSettingsTitle" ResourceKey="ModuleSettings"> @ContainerSettingsComponent
@ModuleSettingsComponent </TabPanel>
</TabPanel> }
} </TabStrip>
@if (_containerSettingsType != null) <br />
{ <button type="button" class="btn btn-success" @onclick="SaveModule">@SharedLocalizer["Save"]</button>
<TabPanel Name="ContainerSettings" Heading="Container Settings" ResourceKey="ContainerSettings"> <NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
@ContainerSettingsComponent <br />
</TabPanel> <br />
} <AuditInfo CreatedBy="@createdby" CreatedOn="@createdon" ModifiedBy="@modifiedby" ModifiedOn="@modifiedon"></AuditInfo>
</TabStrip> </form>
<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>
@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;
@ -179,52 +175,61 @@
private async Task SaveModule() private async Task SaveModule()
{ {
if (!string.IsNullOrEmpty(_title)) validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{ {
var pagemodule = await PageModuleService.GetPageModuleAsync(ModuleState.PageModuleId); if (!string.IsNullOrEmpty(_title))
pagemodule.PageId = int.Parse(_pageId);
pagemodule.Title = _title;
pagemodule.ContainerType = (_containerType != "-") ? _containerType : string.Empty;
if (!string.IsNullOrEmpty(pagemodule.ContainerType) && pagemodule.ContainerType == PageState.Page.DefaultContainerType)
{ {
pagemodule.ContainerType = string.Empty; var pagemodule = await PageModuleService.GetPageModuleAsync(ModuleState.PageModuleId);
} pagemodule.PageId = int.Parse(_pageId);
if (!string.IsNullOrEmpty(pagemodule.ContainerType) && pagemodule.ContainerType == PageState.Site.DefaultContainerType) pagemodule.Title = _title;
{ pagemodule.ContainerType = (_containerType != "-") ? _containerType : string.Empty;
pagemodule.ContainerType = string.Empty; if (!string.IsNullOrEmpty(pagemodule.ContainerType) && pagemodule.ContainerType == PageState.Page.DefaultContainerType)
}
await PageModuleService.UpdatePageModuleAsync(pagemodule);
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane);
var module = ModuleState;
module.AllPages = bool.Parse(_allPages);
module.Permissions = _permissionGrid.GetPermissions();
await ModuleService.UpdateModuleAsync(module);
if (_moduleSettingsType != null)
{
if (_moduleSettings is ISettingsControl moduleSettingsControl)
{ {
// module settings updated using explicit interface pagemodule.ContainerType = string.Empty;
await moduleSettingsControl.UpdateSettings();
} }
else if (!string.IsNullOrEmpty(pagemodule.ContainerType) && pagemodule.ContainerType == PageState.Site.DefaultContainerType)
{ {
// legacy support - module settings updated by convention ( ie. by calling a public method named "UpdateSettings" in settings component ) pagemodule.ContainerType = string.Empty;
_moduleSettings?.GetType().GetMethod("UpdateSettings")?.Invoke(_moduleSettings, null);
} }
} await PageModuleService.UpdatePageModuleAsync(pagemodule);
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane);
if (_containerSettingsType != null && _containerSettings is ISettingsControl containerSettingsControl) var module = ModuleState;
module.AllPages = bool.Parse(_allPages);
module.Permissions = _permissionGrid.GetPermissions();
await ModuleService.UpdateModuleAsync(module);
if (_moduleSettingsType != null)
{
if (_moduleSettings is ISettingsControl moduleSettingsControl)
{
// module settings updated using explicit interface
await moduleSettingsControl.UpdateSettings();
}
else
{
// legacy support - module settings updated by convention ( ie. by calling a public method named "UpdateSettings" in settings component )
_moduleSettings?.GetType().GetMethod("UpdateSettings")?.Invoke(_moduleSettings, null);
}
}
if (_containerSettingsType != null && _containerSettings is ISettingsControl containerSettingsControl)
{
await containerSettingsControl.UpdateSettings();
}
NavigationManager.NavigateTo(NavigateUrl());
}
else
{ {
await containerSettingsControl.UpdateSettings(); AddModuleMessage(Localizer["Message.Required.Title"], MessageType.Warning);
} }
NavigationManager.NavigateTo(NavigateUrl());
} }
else else
{ {
AddModuleMessage(Localizer["Message.Required.Title"], MessageType.Warning); AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
} }
} }

View File

@ -6,176 +6,154 @@
@inject IStringLocalizer<Add> Localizer @inject IStringLocalizer<Add> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer @inject IStringLocalizer<SharedResources> SharedLocalizer
<TabStrip Refresh="@_refresh">
<TabPanel Name="Settings" ResourceKey="Settings"> <form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
@if (_themeList != null) <TabStrip Refresh="@_refresh">
{ <TabPanel Name="Settings" ResourceKey="Settings">
<table class="table table-borderless"> @if (_themeList != null)
<tr> {
<td width="30%"> <div class="container">
<Label For="Name" HelpText="Enter the page name" ResourceKey="Name">Name: </Label> <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>
<td> <div class="col-sm-9">
<input id="Name" class="form-control" @bind="@_name" /> <input id="name" class="form-control" @bind="@_name" required />
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="parent" HelpText="Select the parent for the page in the site hierarchy" ResourceKey="Parent">Parent: </Label>
<Label For="Parent" HelpText="Select the parent for the page in the site hierarchy" ResourceKey="Parent">Parent: </Label> <div class="col-sm-9">
</td> <select id="parent" class="form-select" @onchange="(e => ParentChanged(e))" required>
<td> <option value="-1">&lt;@Localizer["SiteRoot"]&gt;</option>
<select id="Parent" class="form-select" @onchange="(e => ParentChanged(e))"> @foreach (Page page in _pageList)
<option value="-1">&lt;@Localizer["SiteRoot"]&gt;</option> {
@foreach (Page page in _pageList) <option value="@(page.PageId)">@(new string('-', page.Level * 2))@(page.Name)</option>
}
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="insert" HelpText="Select the location where you would like the page to be inserted in relation to other pages" ResourceKey="Insert">Insert: </Label>
<div class="col-sm-9">
<select id="insert" class="form-select" @bind="@_insert" required>
<option value="<<">@Localizer["AtBeginning"]</option>
@if (_children != null && _children.Count > 0)
{
<option value="<">@Localizer["Before"]</option>
<option value=">">@Localizer["After"]</option>
}
<option value=">>">@Localizer["AtEnd"]</option>
</select>
@if (_children != null && _children.Count > 0 && (_insert == "<" || _insert == ">"))
{ {
<option value="@(page.PageId)">@(new string('-', page.Level * 2))@(page.Name)</option> <select class="form-select" @bind="@_childid">
<option value="-1">&lt;@Localizer["Page.Select"]&gt;</option>
@foreach (Page page in _children)
{
<option value="@(page.PageId)">@(page.Name)</option>
}
</select>
} }
</select> </div>
</td> </div>
</tr> <div class="row mb-1 align-items-center">
<tr> <Label Class="col-sm-3" For="navigation" HelpText="Select whether the page is part of the site navigation or hidden" ResourceKey="Navigation">Navigation? </Label>
<td> <div class="col-sm-9">
<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> <select id="navigation" class="form-select" @bind="@_isnavigation" required>
</td>
<td>
<select id="Insert" class="form-select" @bind="@_insert">
<option value="<<">@Localizer["AtBeginning"]</option>
@if (_children != null && _children.Count > 0)
{
<option value="<">@Localizer["Before"]</option>
<option value=">">@Localizer["After"]</option>
}
<option value=">>">@Localizer["AtEnd"]</option>
</select>
@if (_children != null && _children.Count > 0 && (_insert == "<" || _insert == ">"))
{
<select class="form-select" @bind="@_childid">
<option value="-1">&lt;@Localizer["Page.Select"]&gt;</option>
@foreach (Page page in _children)
{
<option value="@(page.PageId)">@(page.Name)</option>
}
</select>
}
</td>
</tr>
<tr>
<td>
<Label For="navigation" HelpText="Select whether the page is part of the site navigation or hidden" ResourceKey="Navigation">Navigation? </Label>
</td>
<td>
<select id="navigation" class="form-select" @bind="@_isnavigation">
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</td>
</tr>
<tr>
<td>
<Label For="clickable" HelpText="Select whether the link in the site navigation is enabled or disabled" ResourceKey="Clickable">Clickable? </Label>
</td>
<td>
<select id="clickable" class="form-select" @bind="@_isclickable">
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</td>
</tr>
<tr>
<td>
<Label For="Path" HelpText="Optionally enter a url path for this page (ie. home ). If you do not provide a url path, the page name will be used." ResourceKey="UrlPath">Url Path: </Label>
</td>
<td>
<input id="Path" class="form-control" @bind="@_path" />
</td>
</tr>
<tr>
<td>
<Label For="Url" HelpText="Optionally enter a url which this page should redirect to when a user navigates to it" ResourceKey="Redirect">Redirect: </Label>
</td>
<td>
<input id="Url" class="form-control" @bind="@_url" />
</td>
</tr>
</table>
<Section Name="Appearance" ResourceKey="Appearance">
<table class="table table-borderless">
<tr>
<td width="30%">
<Label For="Title" HelpText="Optionally enter the page title. If you do not provide a page title, the page name will be used." ResourceKey="Title">Title: </Label>
</td>
<td>
<input id="Title" class="form-control" @bind="@_title" />
</td>
</tr>
<tr>
<td>
<Label For="Theme" HelpText="Select the theme for this page" ResourceKey="Theme">Theme: </Label>
</td>
<td>
<select id="Theme" class="form-select" value="@_themetype" @onchange="(e => ThemeChanged(e))">
@foreach (var theme in _themes)
{
<option value="@theme.TypeName">@theme.Name</option>
}
</select>
</td>
</tr>
<tr>
<td>
<Label For="defaultContainer" HelpText="Select the default container for the page" ResourceKey="DefaultContainer">Default Container: </Label>
</td>
<td>
<select id="defaultContainer" class="form-select" @bind="@_containertype">
<option value="-">&lt;@Localizer["Container.Select"]&gt;</option>
@foreach (var container in _containers)
{
<option value="@container.TypeName">@container.Name</option>
}
</select>
</td>
</tr>
<tr>
<td>
<Label For="Icon" HelpText="Optionally provide an icon class name for this page which will be displayed in the site navigation" ResourceKey="Icon">Icon: </Label>
</td>
<td>
<input id="Icon" class="form-control" @bind="@_icon" />
</td>
</tr>
<tr>
<td>
<Label For="Personalizable" HelpText="Select whether you would like users to be able to personalize this page with their own content" ResourceKey="Personalizable">Personalizable? </Label>
</td>
<td>
<select id="Personalizable" class="form-select" @bind="@_ispersonalizable">
<option value="True">@SharedLocalizer["Yes"]</option> <option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option> <option value="False">@SharedLocalizer["No"]</option>
</select> </select>
</td> </div>
</tr> </div>
</table> <div class="row mb-1 align-items-center">
</Section> <Label Class="col-sm-3" For="clickable" HelpText="Select whether the link in the site navigation is enabled or disabled" ResourceKey="Clickable">Clickable? </Label>
} <div class="col-sm-9">
</TabPanel> <select id="clickable" class="form-select" @bind="@_isclickable" required>
<TabPanel Name="Permissions" ResourceKey="Permissions"> <option value="True">@SharedLocalizer["Yes"]</option>
<table class="table table-borderless"> <option value="False">@SharedLocalizer["No"]</option>
<tr> </select>
<td> </div>
<PermissionGrid EntityName="@EntityNames.Page" Permissions="@_permissions" @ref="_permissionGrid" /> </div>
</td> <div class="row mb-1 align-items-center">
</tr> <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>
</table> <div class="col-sm-9">
</TabPanel> <input id="path" class="form-control" @bind="@_path" />
@if (_themeSettingsType != null) </div>
{ </div>
<TabPanel Name="ThemeSettings" Heading="Theme Settings" ResourceKey="ThemeSettings"> <div class="row mb-1 align-items-center">
@ThemeSettingsComponent <Label Class="col-sm-3" For="url" HelpText="Optionally enter a url which this page should redirect to when a user navigates to it" ResourceKey="Redirect">Redirect: </Label>
<div class="col-sm-9">
<input id="url" class="form-control" @bind="@_url" />
</div>
</div>
</div>
<Section Name="Appearance" ResourceKey="Appearance">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="title" HelpText="Optionally enter the page title. If you do not provide a page title, the page name will be used." ResourceKey="Title">Title: </Label>
<div class="col-sm-9">
<input id="title" class="form-control" @bind="@_title" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="theme" HelpText="Select the theme for this page" ResourceKey="Theme">Theme: </Label>
<div class="col-sm-9">
<select id="theme" class="form-select" value="@_themetype" @onchange="(e => ThemeChanged(e))" required>
@foreach (var theme in _themes)
{
<option value="@theme.TypeName">@theme.Name</option>
}
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="container" HelpText="Select the default container for the page" ResourceKey="DefaultContainer">Default Container: </Label>
<div class="col-sm-9">
<select id="container" class="form-select" @bind="@_containertype" required>
<option value="-">&lt;@Localizer["Container.Select"]&gt;</option>
@foreach (var container in _containers)
{
<option value="@container.TypeName">@container.Name</option>
}
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="icon" HelpText="Optionally provide an icon class name for this page which will be displayed in the site navigation" ResourceKey="Icon">Icon: </Label>
<div class="col-sm-9">
<input id="icon" class="form-control" @bind="@_icon" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="personalizable" HelpText="Select whether you would like users to be able to personalize this page with their own content" ResourceKey="Personalizable">Personalizable? </Label>
<div class="col-sm-9">
<select id="personalizable" class="form-select" @bind="@_ispersonalizable" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
</div>
</Section>
}
</TabPanel> </TabPanel>
} <TabPanel Name="Permissions" ResourceKey="Permissions">
</TabStrip> <div class="container">
<button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button> <div class="row mb-1 align-items-center">
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button> <PermissionGrid EntityName="@EntityNames.Page" Permissions="@_permissions" @ref="_permissionGrid" />
</div>
</div>
</TabPanel>
@if (_themeSettingsType != null)
{
<TabPanel Name="ThemeSettings" Heading="Theme Settings" ResourceKey="ThemeSettings">
@ThemeSettingsComponent
</TabPanel>
}
</TabStrip>
<button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
</form>
@code { @code {
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
@ -187,7 +165,7 @@
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;
@ -204,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()
{ {
@ -300,110 +280,125 @@
private async Task SavePage() private async Task SavePage()
{ {
Page page = null; validated = true;
try var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{ {
if (!string.IsNullOrEmpty(_name) && !string.IsNullOrEmpty(_themetype) && _containertype != "-") Page page = null;
try
{ {
page = new Page(); if (!string.IsNullOrEmpty(_themetype) && _containertype != "-")
page.SiteId = PageState.Page.SiteId;
page.Name = _name;
page.Title = _title;
if (_path == "")
{ {
_path = _name; page = new Page();
} page.SiteId = PageState.Page.SiteId;
page.Name = _name;
if (_path.Contains("/")) page.Title = _title;
{ if (string.IsNullOrEmpty(_path))
_path = _path.Substring(_path.LastIndexOf("/") + 1);
}
if (string.IsNullOrEmpty(_parentid))
{
page.ParentId = null;
page.Path = Utilities.GetFriendlyUrl(_path);
}
else
{
page.ParentId = Int32.Parse(_parentid);
var parent = PageState.Pages.Where(item => item.PageId == page.ParentId).FirstOrDefault();
if (parent.Path == string.Empty)
{ {
page.Path = Utilities.GetFriendlyUrl(parent.Name) + "/" + Utilities.GetFriendlyUrl(_path); _path = _name;
}
if (_path.Contains("/"))
{
_path = _path.Substring(_path.LastIndexOf("/") + 1);
}
if (_parentid == "-1")
{
page.ParentId = null;
page.Path = Utilities.GetFriendlyUrl(_path);
} }
else else
{ {
page.Path = parent.Path + "/" + Utilities.GetFriendlyUrl(_path); page.ParentId = Int32.Parse(_parentid);
var parent = PageState.Pages.Where(item => item.PageId == page.ParentId).FirstOrDefault();
if (parent.Path == string.Empty)
{
page.Path = Utilities.GetFriendlyUrl(parent.Name) + "/" + Utilities.GetFriendlyUrl(_path);
}
else
{
page.Path = parent.Path + "/" + Utilities.GetFriendlyUrl(_path);
}
} }
}
if (!PagePathIsUnique(page.Path, page.SiteId, _pageList)) if(PagePathIsDeleted(page.Path, page.SiteId, _pageList))
{ {
AddModuleMessage(string.Format(Localizer["Message.Page.Exists"], _path), MessageType.Warning); AddModuleMessage(string.Format(Localizer["Message.Page.Deleted"], _path), MessageType.Warning);
return; return;
} }
Page child; if (!PagePathIsUnique(page.Path, page.SiteId, _pageList))
switch (_insert) {
{ AddModuleMessage(string.Format(Localizer["Message.Page.Exists"], _path), MessageType.Warning);
case "<<": return;
page.Order = 0; }
break;
case "<":
child = PageState.Pages.Where(item => item.PageId == _childid).FirstOrDefault();
page.Order = child.Order - 1;
break;
case ">":
child = PageState.Pages.Where(item => item.PageId == _childid).FirstOrDefault();
page.Order = child.Order + 1;
break;
case ">>":
page.Order = int.MaxValue;
break;
}
page.IsNavigation = (_isnavigation == null ? true : Boolean.Parse(_isnavigation)); Page child;
page.IsClickable = (_isclickable == null ? true : Boolean.Parse(_isclickable)); switch (_insert)
page.Url = _url; {
page.ThemeType = (_themetype != "-") ? _themetype : string.Empty; case "<<":
if (!string.IsNullOrEmpty(page.ThemeType) && page.ThemeType == PageState.Site.DefaultThemeType) page.Order = 0;
{ break;
page.ThemeType = string.Empty; case "<":
} child = PageState.Pages.Where(item => item.PageId == _childid).FirstOrDefault();
page.DefaultContainerType = (_containertype != "-") ? _containertype : string.Empty; page.Order = child.Order - 1;
if (!string.IsNullOrEmpty(page.DefaultContainerType) && page.DefaultContainerType == PageState.Site.DefaultContainerType) break;
{ case ">":
page.DefaultContainerType = string.Empty; child = PageState.Pages.Where(item => item.PageId == _childid).FirstOrDefault();
} page.Order = child.Order + 1;
page.Icon = (_icon == null ? string.Empty : _icon); break;
page.Permissions = _permissionGrid.GetPermissions(); case ">>":
page.IsPersonalizable = (_ispersonalizable == null ? false : Boolean.Parse(_ispersonalizable)); page.Order = int.MaxValue;
page.UserId = null; break;
}
page = await PageService.AddPageAsync(page); page.IsNavigation = (_isnavigation == null ? true : Boolean.Parse(_isnavigation));
await PageService.UpdatePageOrderAsync(page.SiteId, page.PageId, page.ParentId); page.IsClickable = (_isclickable == null ? true : Boolean.Parse(_isclickable));
page.Url = _url;
page.ThemeType = (_themetype != "-") ? _themetype : string.Empty;
if (!string.IsNullOrEmpty(page.ThemeType) && page.ThemeType == PageState.Site.DefaultThemeType)
{
page.ThemeType = string.Empty;
}
page.DefaultContainerType = (_containertype != "-") ? _containertype : string.Empty;
if (!string.IsNullOrEmpty(page.DefaultContainerType) && page.DefaultContainerType == PageState.Site.DefaultContainerType)
{
page.DefaultContainerType = string.Empty;
}
page.Icon = (_icon == null ? string.Empty : _icon);
page.Permissions = _permissionGrid.GetPermissions();
page.IsPersonalizable = (_ispersonalizable == null ? false : Boolean.Parse(_ispersonalizable));
page.UserId = null;
await logger.LogInformation("Page Added {Page}", page); page = await PageService.AddPageAsync(page);
if (PageState.QueryString.ContainsKey("cp")) await PageService.UpdatePageOrderAsync(page.SiteId, page.PageId, page.ParentId);
{
NavigationManager.NavigateTo(NavigateUrl(PageState.Pages.First(item => item.PageId == int.Parse(PageState.QueryString["cp"])).Path)); await logger.LogInformation("Page Added {Page}", page);
if (PageState.QueryString.ContainsKey("cp"))
{
NavigationManager.NavigateTo(NavigateUrl(PageState.Pages.First(item => item.PageId == int.Parse(PageState.QueryString["cp"])).Path));
}
else
{
NavigationManager.NavigateTo(NavigateUrl(page.Path));
}
} }
else else
{ {
NavigationManager.NavigateTo(NavigateUrl(page.Path)); AddModuleMessage(Localizer["Message.Required.PageInfo"], MessageType.Warning);
} }
}
else
{
AddModuleMessage(Localizer["Message.Required.PageInfo"], MessageType.Warning);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Saving Page {Page} {Error}", page, ex.Message);
AddModuleMessage(Localizer["Error.Page.Save"], MessageType.Error);
}
} }
catch (Exception ex) else
{ {
await logger.LogError(ex, "Error Saving Page {Page} {Error}", page, ex.Message); AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
AddModuleMessage(Localizer["Error.Page.Save"], MessageType.Error);
} }
} }
@ -423,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

@ -7,192 +7,173 @@
@inject IStringLocalizer<Edit> Localizer @inject IStringLocalizer<Edit> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer @inject IStringLocalizer<SharedResources> SharedLocalizer
<TabStrip Refresh="@_refresh"> <form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<TabPanel Name="Settings" ResourceKey="Settings"> <TabStrip Refresh="@_refresh">
@if (_themeList != null) <TabPanel Name="Settings" ResourceKey="Settings">
{ @if (_themeList != null)
<table class="table table-borderless"> {
<tr> <div class="container">
<td width="30%"> <div class="row mb-1 align-items-center">
<Label For="Name" HelpText="Enter the page name" ResourceKey="Name">Name: </Label> <Label Class="col-sm-3" For="name" HelpText="Enter the page name" ResourceKey="Name">Name: </Label>
</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="Select the parent for the page in the site hierarchy" ResourceKey="Parent">Parent: </Label>
<td> <div class="col-sm-9">
<Label For="Parent" HelpText="Select the parent for the page in the site hierarchy" ResourceKey="Parent">Parent: </Label> <select id="parent" class="form-select" value="@_parentid" @onchange="(e => ParentChanged(e))" required>
</td> <option value="-1">&lt;@Localizer["SiteRoot"]&gt;</option>
<td> @foreach (Page page in _pageList)
<select id="Parent" class="form-select" value="@_parentid" @onchange="(e => ParentChanged(e))">
<option value="-1">&lt;@Localizer["SiteRoot"]&gt;</option>
@foreach (Page page in _pageList)
{
if (page.PageId != _pageId)
{ {
<option value="@(page.PageId)">@(new string('-', page.Level * 2))@(page.Name)</option> if (page.PageId != _pageId)
} {
} <option value="@(page.PageId)">@(new string('-', page.Level * 2))@(page.Name)</option>
</select> }
</td>
</tr>
<tr>
<td>
<Label For="Move" HelpText="Select the location where you would like the page to be moved in relation to other pages" ResourceKey="Move">Move: </Label>
</td>
<td>
<select id="Move" class="form-select" @bind="@_insert">
@if (_parentid == _currentparentid)
{
<option value="=">&lt;@Localizer["ThisLocation.Keep"]&gt;</option>
}
<option value="<<">@Localizer["ToBeginning"]</option>
@if (_children != null && _children.Count > 0)
{
<option value="<">@Localizer["Before"]</option>
<option value=">">@Localizer["After"]</option>
}
<option value=">>">@Localizer["ToEnd"]</option>
</select>
@if (_children != null && _children.Count > 0 && (_insert == "<" || _insert == ">"))
{
<select class="form-select" @bind="@_childid">
<option value="-1">&lt;@Localizer["Page.Select"]&gt;</option>
@foreach (Page page in _children)
{
<option value="@(page.PageId)">@(page.Name)</option>
} }
</select> </select>
} </div>
</td> </div>
</tr> <div class="row mb-1 align-items-center">
<tr> <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>
<td> <div class="col-sm-9">
<Label For="Navigation" HelpText="Select whether the page is part of the site navigation or hidden" ResourceKey="Navigation">Navigation? </Label> <select id="move" class="form-select" @bind="@_insert" required>
</td> @if (_parentid == _currentparentid)
<td>
<select id="Navigation" class="form-select" @bind="@_isnavigation">
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</td>
</tr>
<tr>
<td>
<Label For="Clickablen" HelpText="Select whether the link in the site navigation is enabled or disabled" ResourceKey="Clickable">Clickable? </Label>
</td>
<td>
<select id="Navigation" class="form-select" @bind="@_isclickable">
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</td>
</tr>
<tr>
<td>
<Label For="Path" HelpText="Optionally enter a url path for this page (ie. home ). If you do not provide a url path, the page name will be used." ResourceKey="UrlPath">Url Path: </Label>
</td>
<td>
<input id="Path" class="form-control" @bind="@_path" />
</td>
</tr>
<tr>
<td>
<Label For="Url" HelpText="Optionally enter a url which this page should redirect to when a user navigates to it" ResourceKey="Redirect">Redirect: </Label>
</td>
<td>
<input id="Url" class="form-control" @bind="@_url" />
</td>
</tr>
</table>
<Section Name="Appearance" ResourceKey="Appearance">
<table class="table table-borderless">
<tr>
<td width="30%">
<Label For="Title" HelpText="Optionally enter the page title. If you do not provide a page title, the page name will be used." ResourceKey="Title">Title: </Label>
</td>
<td>
<input id="Title" class="form-control" @bind="@_title" />
</td>
</tr>
<tr>
<td>
<Label For="Theme" HelpText="Select the theme for this page" ResourceKey="Theme">Theme: </Label>
</td>
<td>
<select id="Theme" class="form-select" value="@_themetype" @onchange="(e => ThemeChanged(e))">
@foreach (var theme in _themes)
{ {
<option value="@theme.TypeName">@theme.Name</option> <option value="=">&lt;@Localizer["ThisLocation.Keep"]&gt;</option>
} }
</select> <option value="<<">@Localizer["ToBeginning"]</option>
</td> @if (_children != null && _children.Count > 0)
</tr>
<tr>
<td>
<Label For="defaultContainer" HelpText="Select the default container for the page" ResourceKey="DefaultContainer">Default Container: </Label>
</td>
<td>
<select id="defaultContainer" class="form-select" @bind="@_containertype">
<option value="-">&lt;@Localizer["Container.Select"]&gt;</option>
@foreach (var container in _containers)
{ {
<option value="@container.TypeName">@container.Name</option> <option value="<">@Localizer["Before"]</option>
<option value=">">@Localizer["After"]</option>
} }
<option value=">>">@Localizer["ToEnd"]</option>
</select> </select>
</td> @if (_children != null && _children.Count > 0 && (_insert == "<" || _insert == ">"))
</tr> {
<tr> <select class="form-select" @bind="@_childid">
<td> <option value="-1">&lt;@Localizer["Page.Select"]&gt;</option>
<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> @foreach (Page page in _children)
</td> {
<td> <option value="@(page.PageId)">@(page.Name)</option>
<input id="Icon" class="form-control" @bind="@_icon" /> }
</td> </select>
</tr> }
<tr> </div>
<td> </div>
<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> <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>
<td> <div class="col-sm-9">
<select id="Personalizable" class="form-select" @bind="@_ispersonalizable"> <select id="navigation" class="form-select" @bind="@_isnavigation" required>
<option value="True">@SharedLocalizer["Yes"]</option> <option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option> <option value="False">@SharedLocalizer["No"]</option>
</select> </select>
</td> </div>
</tr> </div>
</table> <div class="row mb-1 align-items-center">
</Section> <Label Class="col-sm-3" For="clickable" HelpText="Select whether the link in the site navigation is enabled or disabled" ResourceKey="Clickable">Clickable? </Label>
<br /><br /> <div class="col-sm-9">
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon" DeletedBy="@_deletedby" DeletedOn="@_deletedon"></AuditInfo> <select id="clickable" class="form-select" @bind="@_isclickable" required>
} <option value="True">@SharedLocalizer["Yes"]</option>
</TabPanel> <option value="False">@SharedLocalizer["No"]</option>
<TabPanel Name="Permissions" ResourceKey="Permissions"> </select>
@if (_permissions != null) </div>
{ </div>
<table class="table table-borderless"> <div class="row mb-1 align-items-center">
<tr> <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">
<PermissionGrid EntityName="@EntityNames.Page" Permissions="@_permissions" @ref="_permissionGrid" /> <input id="path" class="form-control" @bind="@_path" maxlength="256"/>
</td> </div>
</tr> </div>
</table> <div class="row mb-1 align-items-center">
} <Label Class="col-sm-3" For="url" HelpText="Optionally enter a url which this page should redirect to when a user navigates to it" ResourceKey="Redirect">Redirect: </Label>
</TabPanel> <div class="col-sm-9">
@if (_themeSettingsType != null) <input id="url" class="form-control" @bind="@_url" maxlength="500"/>
{ </div>
<TabPanel Name="ThemeSettings" Heading="Theme Settings" ResourceKey="ThemeSettings"> </div>
@ThemeSettingsComponent </div>
<Section Name="Appearance" ResourceKey="Appearance">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="title" HelpText="Optionally enter the page title. If you do not provide a page title, the page name will be used." ResourceKey="Title">Title: </Label>
<div class="col-sm-9">
<input id="title" class="form-control" @bind="@_title" maxlength="200"/>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="theme" HelpText="Select the theme for this page" ResourceKey="Theme">Theme: </Label>
<div class="col-sm-9">
<select id="theme" class="form-select" value="@_themetype" @onchange="(e => ThemeChanged(e))" required>
@foreach (var theme in _themes)
{
<option value="@theme.TypeName">@theme.Name</option>
}
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="container" HelpText="Select the default container for the page" ResourceKey="DefaultContainer">Default Container: </Label>
<div class="col-sm-9">
<select id="container" class="form-select" @bind="@_containertype" required>
<option value="-">&lt;@Localizer["Container.Select"]&gt;</option>
@foreach (var container in _containers)
{
<option value="@container.TypeName">@container.Name</option>
}
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="icon" HelpText="Optionally provide an icon class name for this page which will be displayed in the site navigation" ResourceKey="Icon">Icon: </Label>
<div class="col-sm-9">
<input id="icon" class="form-control" @bind="@_icon" maxlength="50"/>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="personalizable" HelpText="Select whether you would like users to be able to personalize this page with their own content" ResourceKey="Personalizable">Personalizable? </Label>
<div class="col-sm-9">
<select id="personalizable" class="form-select" @bind="@_ispersonalizable" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
</div>
</Section>
<br /><br />
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon" DeletedBy="@_deletedby" DeletedOn="@_deletedon"></AuditInfo>
}
</TabPanel> </TabPanel>
} <TabPanel Name="Permissions" ResourceKey="Permissions">
</TabStrip> @if (_permissions != null)
<button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button> {
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button> <div class="container">
<div class="row mb-1 align-items-center">
<PermissionGrid EntityName="@EntityNames.Page" Permissions="@_permissions" @ref="_permissionGrid" />
</div>
</div>
}
</TabPanel>
@if (_themeSettingsType != null)
{
<TabPanel Name="ThemeSettings" Heading="Theme Settings" ResourceKey="ThemeSettings">
@ThemeSettingsComponent
</TabPanel>
<br />
}
</TabStrip>
<button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
</form>
@code { @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>();
@ -202,7 +183,7 @@
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;
@ -251,7 +232,7 @@
if (page.ParentId == null) if (page.ParentId == null)
{ {
_parentid = string.Empty; _parentid = "-1";
} }
else else
{ {
@ -375,134 +356,142 @@
private async Task SavePage() private async Task SavePage()
{ {
Page page = null; validated = true;
try var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{ {
if (_name != string.Empty && !string.IsNullOrEmpty(_themetype) && _containertype != "-") Page page = null;
try
{ {
page = PageState.Pages.FirstOrDefault(item => item.PageId == _pageId); if (!string.IsNullOrEmpty(_themetype) && _containertype != "-")
string currentPath = page.Path; {
page = PageState.Pages.FirstOrDefault(item => item.PageId == _pageId);
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;
} }
if (_path.Contains("/")) if (_path.Contains("/"))
{
_path = _path.Substring(_path.LastIndexOf("/") + 1);
}
if (string.IsNullOrEmpty(_parentid) || _parentid == "-1")
{
page.ParentId = null;
page.Path = Utilities.GetFriendlyUrl(_path);
}
else
{
page.ParentId = Int32.Parse(_parentid);
Page parent = PageState.Pages.FirstOrDefault(item => item.PageId == page.ParentId);
if (parent.Path == string.Empty)
{ {
page.Path = Utilities.GetFriendlyUrl(parent.Name) + "/" + Utilities.GetFriendlyUrl(_path); _path = _path.Substring(_path.LastIndexOf("/") + 1);
}
if (_parentid == "-1")
{
page.ParentId = null;
page.Path = Utilities.GetFriendlyUrl(_path);
} }
else else
{ {
page.Path = parent.Path + "/" + Utilities.GetFriendlyUrl(_path); page.ParentId = Int32.Parse(_parentid);
Page parent = PageState.Pages.FirstOrDefault(item => item.PageId == page.ParentId);
if (parent.Path == string.Empty)
{
page.Path = Utilities.GetFriendlyUrl(parent.Name) + "/" + Utilities.GetFriendlyUrl(_path);
}
else
{
page.Path = parent.Path + "/" + Utilities.GetFriendlyUrl(_path);
}
} }
}
if (!PagePathIsUnique(page.Path, page.SiteId, page.PageId, _pageList)) if (!PagePathIsUnique(page.Path, page.SiteId, page.PageId, _pageList))
{
AddModuleMessage(string.Format(Localizer["Mesage.Page.PathExists"], _path), MessageType.Warning);
return;
}
if (_insert != "=")
{
Page child;
switch (_insert)
{ {
case "<<": AddModuleMessage(string.Format(Localizer["Mesage.Page.PathExists"], _path), MessageType.Warning);
page.Order = 0; return;
break;
case "<":
child = PageState.Pages.FirstOrDefault(item => item.PageId == _childid);
if (child != null) page.Order = child.Order - 1;
break;
case ">":
child = PageState.Pages.FirstOrDefault(item => item.PageId == _childid);
if (child != null) page.Order = child.Order + 1;
break;
case ">>":
page.Order = int.MaxValue;
break;
} }
}
page.IsNavigation = (_isnavigation == null || Boolean.Parse(_isnavigation));
page.IsClickable = (_isclickable == null ? true : Boolean.Parse(_isclickable));
page.Url = _url;
page.ThemeType = (_themetype != "-") ? _themetype : string.Empty;
if (!string.IsNullOrEmpty(page.ThemeType) && page.ThemeType == PageState.Site.DefaultThemeType)
{
page.ThemeType = string.Empty;
}
page.DefaultContainerType = (_containertype != "-") ? _containertype : string.Empty;
if (!string.IsNullOrEmpty(page.DefaultContainerType) && page.DefaultContainerType == PageState.Site.DefaultContainerType)
{
page.DefaultContainerType = string.Empty;
}
page.Icon = _icon ?? string.Empty;
page.Permissions = _permissionGrid.GetPermissions();
page.IsPersonalizable = (_ispersonalizable != null && Boolean.Parse(_ispersonalizable));
page.UserId = null;
page = await PageService.UpdatePageAsync(page); if (_insert != "=")
await PageService.UpdatePageOrderAsync(page.SiteId, page.PageId, page.ParentId); {
if (_currentparentid == string.Empty) Page child;
{ switch (_insert)
await PageService.UpdatePageOrderAsync(page.SiteId, page.PageId, null); {
case "<<":
page.Order = 0;
break;
case "<":
child = PageState.Pages.FirstOrDefault(item => item.PageId == _childid);
if (child != null) page.Order = child.Order - 1;
break;
case ">":
child = PageState.Pages.FirstOrDefault(item => item.PageId == _childid);
if (child != null) page.Order = child.Order + 1;
break;
case ">>":
page.Order = int.MaxValue;
break;
}
}
page.IsNavigation = (_isnavigation == null || Boolean.Parse(_isnavigation));
page.IsClickable = (_isclickable == null ? true : Boolean.Parse(_isclickable));
page.Url = _url;
page.ThemeType = (_themetype != "-") ? _themetype : string.Empty;
if (!string.IsNullOrEmpty(page.ThemeType) && page.ThemeType == PageState.Site.DefaultThemeType)
{
page.ThemeType = string.Empty;
}
page.DefaultContainerType = (_containertype != "-") ? _containertype : string.Empty;
if (!string.IsNullOrEmpty(page.DefaultContainerType) && page.DefaultContainerType == PageState.Site.DefaultContainerType)
{
page.DefaultContainerType = string.Empty;
}
page.Icon = _icon ?? string.Empty;
page.Permissions = _permissionGrid.GetPermissions();
page.IsPersonalizable = (_ispersonalizable != null && Boolean.Parse(_ispersonalizable));
page.UserId = null;
page = await PageService.UpdatePageAsync(page);
await PageService.UpdatePageOrderAsync(page.SiteId, page.PageId, page.ParentId);
if (_currentparentid == string.Empty)
{
await PageService.UpdatePageOrderAsync(page.SiteId, page.PageId, null);
}
else
{
await PageService.UpdatePageOrderAsync(page.SiteId, page.PageId, int.Parse(_currentparentid));
}
// update child paths
if (_parentid != _currentparentid)
{
foreach (Page p in PageState.Pages.Where(item => item.Path.StartsWith(currentPath)))
{
p.Path = p.Path.Replace(currentPath, page.Path);
await PageService.UpdatePageAsync(p);
}
}
if (_themeSettingsType != null && _themeSettings is ISettingsControl themeSettingsControl)
{
await themeSettingsControl.UpdateSettings();
}
await logger.LogInformation("Page Saved {Page}", page);
if (PageState.QueryString.ContainsKey("cp"))
{
NavigationManager.NavigateTo(NavigateUrl(PageState.Pages.First(item => item.PageId == int.Parse(PageState.QueryString["cp"])).Path));
}
else
{
NavigationManager.NavigateTo(NavigateUrl(page.Path));
}
} }
else else
{ {
await PageService.UpdatePageOrderAsync(page.SiteId, page.PageId, int.Parse(_currentparentid)); AddModuleMessage(Localizer["Message.Required.PageInfo"], MessageType.Warning);
}
// update child paths
if (_parentid != _currentparentid)
{
foreach (Page p in PageState.Pages.Where(item => item.Path.StartsWith(currentPath)))
{
p.Path = p.Path.Replace(currentPath, page.Path);
await PageService.UpdatePageAsync(p);
}
}
if (_themeSettingsType != null && _themeSettings is ISettingsControl themeSettingsControl)
{
await themeSettingsControl.UpdateSettings();
}
await logger.LogInformation("Page Saved {Page}", page);
if (PageState.QueryString.ContainsKey("cp"))
{
NavigationManager.NavigateTo(NavigateUrl(PageState.Pages.First(item => item.PageId == int.Parse(PageState.QueryString["cp"])).Path));
}
else
{
NavigationManager.NavigateTo(NavigateUrl(page.Path));
} }
} }
else catch (Exception ex)
{ {
AddModuleMessage(Localizer["Message.Required.PageInfo"], MessageType.Warning); await logger.LogError(ex, "Error Saving Page {Page} {Error}", page, ex.Message);
AddModuleMessage(Localizer["Error.Page.Save"], MessageType.Error);
} }
} }
catch (Exception ex) else
{ {
await logger.LogError(ex, "Error Saving Page {Page} {Error}", page, ex.Message); AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
AddModuleMessage(Localizer["Error.Page.Save"], MessageType.Error);
} }
} }

View File

@ -5,105 +5,90 @@
@inject IStringLocalizer<Edit> Localizer @inject IStringLocalizer<Edit> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer @inject IStringLocalizer<SharedResources> SharedLocalizer
<table class="table table-borderless"> <form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<tr> <div class="container">
<td width="30%"> <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> </select>
<tr> </div>
<td> </div>
<Label For="options" HelpText="A comma delimited list of options the user can select from" ResourceKey="Options">Options: </Label> <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>
<td> <div class="col-sm-9">
<input id="options" class="form-control" @bind="@_options" /> <select id="private" class="form-select" @bind="@_isprivate" required>
</td> <option value="True">@SharedLocalizer["Yes"]</option>
</tr> <option value="False">@SharedLocalizer["No"]</option>
<tr> </select>
<td> </div>
<Label For="required" HelpText="Should a user be required to provide a value for this profile item?" ResourceKey="Required">Required? </Label> </div>
</td> </div>
<td>
<select id="required" class="form-select" @bind="@_isrequired">
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</td>
</tr>
<tr>
<td>
<Label For="private" HelpText="Should this profile item be visible to all users?" ResourceKey="Private">Private? </Label>
</td>
<td>
<select id="private" class="form-select" @bind="@_isprivate">
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</td>
</tr>
</table>
<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 />
<br /> <button type="button" class="btn btn-success" @onclick="SaveProfile">@SharedLocalizer["Save"]</button>
<AuditInfo CreatedBy="@createdby" CreatedOn="@createdon" ModifiedBy="@modifiedby" ModifiedOn="@modifiedon"></AuditInfo> <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;
@ -159,45 +144,54 @@
private async Task SaveProfile() private async Task SaveProfile()
{ {
try validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{ {
Profile profile; try
if (_profileid != -1)
{ {
profile = await ProfileService.GetProfileAsync(_profileid); Profile profile;
} if (_profileid != -1)
else {
{ profile = await ProfileService.GetProfileAsync(_profileid);
profile = new Profile(); }
} else
{
profile = new Profile();
}
profile.SiteId = PageState.Site.SiteId; profile.SiteId = PageState.Site.SiteId;
profile.Name = _name; profile.Name = _name;
profile.Title = _title; profile.Title = _title;
profile.Description = _description; profile.Description = _description;
profile.Category = _category; profile.Category = _category;
profile.ViewOrder = int.Parse(_vieworder); profile.ViewOrder = int.Parse(_vieworder);
profile.MaxLength = int.Parse(_maxlength); profile.MaxLength = int.Parse(_maxlength);
profile.DefaultValue = _defaultvalue; profile.DefaultValue = _defaultvalue;
profile.Options = _options; profile.Options = _options;
profile.IsRequired = (_isrequired == null ? false : Boolean.Parse(_isrequired)); profile.IsRequired = (_isrequired == null ? false : Boolean.Parse(_isrequired));
profile.IsPrivate = (_isprivate == null ? false : Boolean.Parse(_isprivate)); profile.IsPrivate = (_isprivate == null ? false : Boolean.Parse(_isprivate));
if (_profileid != -1) if (_profileid != -1)
{ {
profile = await ProfileService.UpdateProfileAsync(profile); profile = await ProfileService.UpdateProfileAsync(profile);
} }
else else
{ {
profile = await ProfileService.AddProfileAsync(profile); profile = await ProfileService.AddProfileAsync(profile);
} }
await logger.LogInformation("Profile Saved {Profile}", profile); await logger.LogInformation("Profile Saved {Profile}", profile);
NavigationManager.NavigateTo(NavigateUrl()); NavigationManager.NavigateTo(NavigateUrl());
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Saving Profile {ProfleId} {Error}", _profileid, ex.Message);
AddModuleMessage(Localizer["Error.Profile.Save"], MessageType.Error);
}
} }
catch (Exception ex) else
{ {
await logger.LogError(ex, "Error Saving Profile {ProfleId} {Error}", _profileid, ex.Message); AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
AddModuleMessage(Localizer["Error.Profile.Save"], MessageType.Error);
} }
} }
} }

View File

@ -16,52 +16,43 @@
</Authorized> </Authorized>
<NotAuthorized> <NotAuthorized>
<ModuleMessage Message="@Localizer["Info.Registration.InvalidEmail"]" Type="MessageType.Info" /> <ModuleMessage Message="@Localizer["Info.Registration.InvalidEmail"]" Type="MessageType.Info" />
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<table class="table table-borderless"> <div class="container">
<tr> <div class="row mb-1 align-items-center">
<td width="30%"> <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>
<Label For="username" HelpText="Your username. Note that this field can not be modified once it is saved." ResourceKey="Username"></Label> <div class="col-sm-9">
</td> <input id="username" class="form-control" @bind="@_username" maxlength="256" required />
<td> </div>
<input id="username" 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="Please choose a sufficiently secure password and enter it here" ResourceKey="Password"></Label>
<tr> <div class="col-sm-9">
<td> <input id="password" type="password" class="form-control" @bind="@_password" autocomplete="new-password" required />
<Label For="password" HelpText="If you wish to change your password you can enter it here. Please choose a sufficiently secure password." ResourceKey="Password"></Label> </div>
</td> </div>
<td> <div class="row mb-1 align-items-center">
<input id="password" type="password" class="form-control" @bind="@_password" autocomplete="new-password" /> <Label Class="col-sm-3" For="confirm" HelpText="Enter your password again to confirm it matches the value entered above" ResourceKey="Confirm"></Label>
</td> <div class="col-sm-9">
</tr> <input id="confirm" type="password" class="form-control" @bind="@_confirm" autocomplete="new-password" required />
<tr> </div>
<td> </div>
<Label For="confirm" HelpText="If you are changing your password you must enter it again to confirm it matches" ResourceKey="Confirm"></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 id="confirm" type="password" class="form-control" @bind="@_confirm" autocomplete="new-password" /> <input id="email" class="form-control" @bind="@_email" maxlength="256" required />
</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="email" HelpText="Your email address where you wish to receive notifications" ResourceKey="Email"></Label> <div class="col-sm-9">
</td> <input id="displayname" class="form-control" @bind="@_displayname" maxlength="50" />
<td> </div>
<input id="email" class="form-control" @bind="@_email" /> </div>
</td> </div>
</tr> <br />
<tr> <button type="button" class="btn btn-primary" @onclick="Register">@Localizer["Register"]</button>
<td> <button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
<Label For="displayname" HelpText="Your full name" ResourceKey="DisplayName"></Label> </form>
</td>
<td>
<input id="displayname" class="form-control" @bind="@_displayname" />
</td>
</tr>
</table>
<button type="button" class="btn btn-primary" @onclick="Register">@Localizer["Register"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
</NotAuthorized> </NotAuthorized>
</AuthorizeView> </AuthorizeView>
} }
@ -72,6 +63,8 @@ else
@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;
@ -81,49 +74,58 @@ else
private async Task Register() private async Task Register()
{ {
try validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{ {
bool _isEmailValid = Utilities.IsValidEmail(_email); try
if (_username != "" && _password != "" && _confirm != "" && _isEmailValid)
{ {
if (_password == _confirm) bool _isEmailValid = Utilities.IsValidEmail(_email);
{
var user = new User
{
SiteId = PageState.Site.SiteId,
Username = _username,
DisplayName = (_displayname == string.Empty ? _username : _displayname),
Email = _email,
Password = _password
};
user = await UserService.AddUserAsync(user);
if (user != null) if (_isEmailValid)
{
if (_password == _confirm)
{ {
await logger.LogInformation("User Created {Username} {Email}", _username, _email); var user = new User
AddModuleMessage(Localizer["Info.User.AccountCreate"], MessageType.Info); {
SiteId = PageState.Site.SiteId,
Username = _username,
DisplayName = (_displayname == string.Empty ? _username : _displayname),
Email = _email,
Password = _password
};
user = await UserService.AddUserAsync(user);
if (user != null)
{
await logger.LogInformation("User Created {Username} {Email}", _username, _email);
AddModuleMessage(Localizer["Info.User.AccountCreate"], MessageType.Info);
}
else
{
await logger.LogError("Error Adding User {Username} {Email}", _username, _email);
AddModuleMessage(Localizer["Error.User.AddInfo"], MessageType.Error);
}
} }
else else
{ {
await logger.LogError("Error Adding User {Username} {Email}", _username, _email); AddModuleMessage(Localizer["Message.Password.NoMatch"], MessageType.Warning);
AddModuleMessage(Localizer["Error.User.AddInfo"], MessageType.Error);
} }
} }
else else
{ {
AddModuleMessage(Localizer["Message.Password.NoMatch"], MessageType.Warning); AddModuleMessage(Localizer["Message.Required.UserInfo"], MessageType.Warning);
} }
} }
else catch (Exception ex)
{ {
AddModuleMessage(Localizer["Message.Required.UserInfo"], MessageType.Warning); await logger.LogError(ex, "Error Adding User {Username} {Email} {Error}", _username, _email, ex.Message);
AddModuleMessage(Localizer["Error.User.Add"], MessageType.Error);
} }
} }
catch (Exception ex) else
{ {
await logger.LogError(ex, "Error Adding User {Username} {Email} {Error}", _username, _email, ex.Message); AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
AddModuleMessage(Localizer["Error.User.Add"], MessageType.Error);
} }
} }

View File

@ -5,24 +5,28 @@
@inject IStringLocalizer<Index> Localizer @inject IStringLocalizer<Index> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer @inject IStringLocalizer<SharedResources> SharedLocalizer
<div class="container"> <form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<div class="form-group"> <div class="container">
<label for="Username" class="control-label">@SharedLocalizer["Username"] </label> <div class="form-group">
<input type="text" class="form-control" placeholder="Username" @bind="@_username" readonly id="Username" /> <label for="Username" class="control-label">@SharedLocalizer["Username"] </label>
<input type="text" class="form-control" placeholder="Username" @bind="@_username" readonly id="Username" />
</div>
<div class="form-group">
<label for="Password" class="control-label">@SharedLocalizer["Password"] </label>
<input type="password" class="form-control" placeholder="Password" @bind="@_password" id="Password" required />
</div>
<div class="form-group">
<label for="Confirm" class="control-label">@Localizer["Password.Confirm"] </label>
<input type="password" class="form-control" placeholder="Password" @bind="@_confirm" id="Confirm" required />
</div>
<button type="button" class="btn btn-primary" @onclick="Reset">@Localizer["Password.Reset"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
</div> </div>
<div class="form-group"> </form>
<label for="Password" class="control-label">@SharedLocalizer["Password"] </label>
<input type="password" class="form-control" placeholder="Password" @bind="@_password" id="Password" />
</div>
<div class="form-group">
<label for="Confirm" class="control-label">@Localizer["Password.Confirm"] </label>
<input type="password" class="form-control" placeholder="Password" @bind="@_confirm" id="Confirm" />
</div>
<button type="button" class="btn btn-primary" @onclick="Reset">@Localizer["Password.Reset"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
</div>
@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;
@ -43,45 +47,54 @@
private async Task Reset() private async Task Reset()
{ {
try validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{ {
if (_username != string.Empty && _password != string.Empty && _confirm != string.Empty) try
{ {
if (_password == _confirm) if (_username != string.Empty && _password != string.Empty && _confirm != string.Empty)
{ {
var user = new User if (_password == _confirm)
{ {
SiteId = PageState.Site.SiteId, var user = new User
Username = _username, {
Password = _password SiteId = PageState.Site.SiteId,
}; Username = _username,
user = await UserService.ResetPasswordAsync(user, PageState.QueryString["token"]); Password = _password
};
user = await UserService.ResetPasswordAsync(user, PageState.QueryString["token"]);
if (user != null) if (user != null)
{ {
await logger.LogInformation("User Password Reset {Username}", _username); await logger.LogInformation("User Password Reset {Username}", _username);
NavigationManager.NavigateTo(NavigateUrl("login")); NavigationManager.NavigateTo(NavigateUrl("login"));
}
else
{
await logger.LogError("Error Resetting User Password {Username}", _username);
AddModuleMessage(Localizer["Error.Password.ResetInfo"], MessageType.Error);
}
} }
else else
{ {
await logger.LogError("Error Resetting User Password {Username}", _username); AddModuleMessage(Localizer["Message.Password.NoMatch"], MessageType.Warning);
AddModuleMessage(Localizer["Error.Password.ResetInfo"], MessageType.Error);
} }
} }
else else
{ {
AddModuleMessage(Localizer["Message.Password.NoMatch"], MessageType.Warning); AddModuleMessage(Localizer["Message.Required.UserInfo"], MessageType.Warning);
} }
} }
else catch (Exception ex)
{ {
AddModuleMessage(Localizer["Message.Required.UserInfo"], MessageType.Warning); await logger.LogError(ex, "Error Resetting User Password {Username} {Error}", _username, ex.Message);
AddModuleMessage(Localizer["Error.Password.Reset"], MessageType.Error);
} }
} }
catch (Exception ex) else
{ {
await logger.LogError(ex, "Error Resetting User Password {Username} {Error}", _username, ex.Message); AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
AddModuleMessage(Localizer["Error.Password.Reset"], MessageType.Error);
} }
} }

View File

@ -6,37 +6,32 @@
@inject IStringLocalizer<SharedResources> SharedLocalizer @inject IStringLocalizer<SharedResources> SharedLocalizer
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate> <form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<table class="table table-borderless"> <div class="container">
<tr> <div class="row mb-1 align-items-center">
<td width="30%"> <Label Class="col-sm-3" For="name" HelpText="Name Of The Role" ResourceKey="Name">Name:</Label>
<Label For="name" HelpText="Name Of The Role" ResourceKey="Name">Name:</Label> <div class="col-sm-9">
</td>
<td>
<input id="name" class="form-control" @bind="@_name" maxlength="256" required /> <input id="name" class="form-control" @bind="@_name" maxlength="256" required />
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="description" HelpText="A Short Description Of The Role Which Describes Its Purpose" ResourceKey="Description">Description:</Label>
<Label For="description" HelpText="A Short Description Of The Role Which Describes Its Purpose" ResourceKey="Description">Description:</Label> <div class="col-sm-9">
</td>
<td>
<textarea id="description" class="form-control" @bind="@_description" rows="5" maxlength="256" required></textarea> <textarea id="description" class="form-control" @bind="@_description" rows="5" maxlength="256" required></textarea>
</td> </div>
</tr> </div>
<tr> <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>
<Label For="isautoassigned" HelpText="Indicates Whether Or Not New Users Are Automatically Assigned To This Role" ResourceKey="AutoAssigned">Auto Assigned?</Label> <div class="col-sm-9">
</td> <select id="isautoassigned" class="form-select" @bind="@_isautoassigned" required>
<td>
<select id="isautoassigned" class="form-select" @bind="@_isautoassigned">
<option value="True">@SharedLocalizer["Yes"]</option> <option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option> <option value="False">@SharedLocalizer["No"]</option>
</select> </select>
</td> </div>
</tr> </div>
</table> <br /><br />
<button type="button" class="btn btn-success" @onclick="SaveRole">@SharedLocalizer["Save"]</button> <button type="button" class="btn btn-success" @onclick="SaveRole">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink> <NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
</div>
</form> </form>
@code { @code {
@ -77,7 +72,7 @@
} }
else else
{ {
AddModuleMessage(Localizer["Message.InfoRequired"], MessageType.Warning); AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
} }
} }

View File

@ -6,39 +6,34 @@
@inject IStringLocalizer<SharedResources> SharedLocalizer @inject IStringLocalizer<SharedResources> SharedLocalizer
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate> <form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<table class="table table-borderless"> <div class="container">
<tr> <div class="row mb-1 align-items-center">
<td width="30%"> <Label Class="col-sm-3" For="name" HelpText="Name Of The Role" ResourceKey="Name">Name:</Label>
<Label For="name" HelpText="Name Of The Role" ResourceKey="Name">Name:</Label> <div class="col-sm-9">
</td>
<td>
<input id="name" class="form-control" @bind="@_name" maxlength="256" required /> <input id="name" class="form-control" @bind="@_name" maxlength="256" required />
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="description" HelpText="A Short Description Of The Role Which Describes Its Purpose" ResourceKey="Description">Description:</Label>
<Label For="description" HelpText="A Short Description Of The Role Which Describes Its Purpose" ResourceKey="Description">Description:</Label> <div class="col-sm-9">
</td>
<td>
<textarea id="description" class="form-control" @bind="@_description" rows="5" maxlength="256" required></textarea> <textarea id="description" class="form-control" @bind="@_description" rows="5" maxlength="256" required></textarea>
</td> </div>
</tr> </div>
<tr> <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>
<Label For="isautoassigned" HelpText="Indicates Whether Or Not New Users Are Automatically Assigned To This Role" ResourceKey="AutoAssigned">Auto Assigned?</Label> <div class="col-sm-9">
</td> <select id="isautoassigned" class="form-select" @bind="@_isautoassigned" required>
<td>
<select id="isautoassigned" class="form-select" @bind="@_isautoassigned">
<option value="True">@SharedLocalizer["Yes"]</option> <option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option> <option value="False">@SharedLocalizer["No"]</option>
</select> </select>
</td> </div>
</tr> </div>
</table> <br /><br />
<button type="button" class="btn btn-success" @onclick="SaveRole">@SharedLocalizer["Save"]</button> <button type="button" class="btn btn-success" @onclick="SaveRole">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["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>
</div>
</form> </form>
@code { @code {
@ -106,7 +101,7 @@
} }
else else
{ {
AddModuleMessage(Localizer["Message.InfoRequired"], MessageType.Warning); AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
} }
} }
} }

View File

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

View File

@ -13,251 +13,217 @@
@if (_initialized) @if (_initialized)
{ {
<table class="table table-borderless"> <form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<tr> <div class="container">
<td width="30%"> <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="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> @if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
</td> {
<td> <textarea id="alias" class="form-control" @bind="@_urls" rows="3" required></textarea>
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) }
{ else
<textarea id="alias" class="form-control" @bind="@_urls" rows="3"></textarea> {
} <textarea id="alias" class="form-control" @bind="@_urls" rows="3" readonly></textarea>
else }
{ </div>
<textarea id="alias" class="form-control" @bind="@_urls" rows="3" readonly></textarea> </div>
} <div class="row mb-1 align-items-center">
</td> <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>
</tr> <div class="col-sm-9">
<select id="allowRegister" class="form-select" @bind="@_allowregistration" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="isDeleted" HelpText="Is this site deleted?" ResourceKey="IsDeleted">Is Deleted? </Label>
<div class="col-sm-9">
<select id="isDeleted" class="form-select" @bind="@_isdeleted" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
</div>
<Section Name="Appearance" Heading="Appearance" ResourceKey="Appearance">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="logo" HelpText="Specify a logo for the site" ResourceKey="Logo">Logo: </Label>
<div class="col-sm-9">
<FileManager FileId="@_logofileid" Filter="@Constants.ImageFiles" @ref="_logofilemanager" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="favicon" HelpText="Specify a Favicon" ResourceKey="FavoriteIcon">Favicon: </Label>
<div class="col-sm-9">
<FileManager FileId="@_faviconfileid" Filter="ico" @ref="_faviconfilemanager" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="defaultTheme" HelpText="Select the sites default theme" ResourceKey="DefaultTheme">Default Theme: </Label>
<div class="col-sm-9">
<select id="defaultTheme" class="form-select" value="@_themetype" @onchange="(e => ThemeChanged(e))" required>
<option value="-">&lt;@Localizer["Theme.Select"]&gt;</option>
@foreach (var theme in _themes)
{
<option value="@theme.TypeName">@theme.Name</option>
}
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="defaultContainer" HelpText="Select the default container for the site" ResourceKey="DefaultContainer">Default Container: </Label>
<div class="col-sm-9">
<select id="defaultContainer" class="form-select" @bind="@_containertype" required>
<option value="-">&lt;@Localizer["Container.Select"]&gt;</option>
@foreach (var container in _containers)
{
<option value="@container.TypeName">@container.Name</option>
}
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="defaultAdminContainer" HelpText="Select the default admin container for the site" ResourceKey="DefaultAdminContainer">Default Admin Container: </Label>
<div class="col-sm-9">
<select id="defaultAdminContainer" class="form-select" @bind="@_admincontainertype" required>
<option value="-">&lt;@Localizer["Container.Select"]&gt;</option>
<option value="@Constants.DefaultAdminContainer">&lt;@Localizer["DefaultAdminContainer"]&gt;</option>
@foreach (var container in _containers)
{
<option value="@container.TypeName">@container.Name</option>
}
</select>
</div>
</div>
<tr> </div>
<td>
<Label For="allowRegister" HelpText="Do you want the users to be able to register for an account on the site" ResourceKey="AllowRegistration">Allow User Registration? </Label>
</td>
<td>
<select id="allowRegister" class="form-select" @bind="@_allowregistration">
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</td>
</tr>
<tr>
<td>
<Label For="isDeleted" HelpText="Is this site deleted?" ResourceKey="IsDeleted">Is Deleted? </Label>
</td>
<td>
<select id="isDeleted" class="form-select" @bind="@_isdeleted">
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</td>
</tr>
</table>
<Section Name="Appearance" Heading="Appearance" ResourceKey="Appearance">
<table class="table table-borderless">
<tr>
<td width="30%">
<Label For="logo" HelpText="Specify a logo for the site" ResourceKey="Logo">Logo: </Label>
</td>
<td>
<FileManager FileId="@_logofileid" Filter="@Constants.ImageFiles" @ref="_logofilemanager" />
</td>
</tr>
<tr>
<td>
<Label For="favicon" HelpText="Specify a Favicon" ResourceKey="FavoriteIcon">Favicon: </Label>
</td>
<td>
<FileManager FileId="@_faviconfileid" Filter="ico" @ref="_faviconfilemanager" />
</td>
</tr>
<tr>
<td>
<Label For="defaultTheme" HelpText="Select the sites default theme" ResourceKey="DefaultTheme">Default Theme: </Label>
</td>
<td>
<select id="defaultTheme" class="form-select" value="@_themetype" @onchange="(e => ThemeChanged(e))">
<option value="-">&lt;@Localizer["Theme.Select"]&gt;</option>
@foreach (var theme in _themes)
{
<option value="@theme.TypeName">@theme.Name</option>
}
</select>
</td>
</tr>
<tr>
<td>
<Label For="defaultContainer" HelpText="Select the default container for the site" ResourceKey="DefaultContainer">Default Container: </Label>
</td>
<td>
<select id="defaultContainer" class="form-select" @bind="@_containertype">
<option value="-">&lt;@Localizer["Container.Select"]&gt;</option>
@foreach (var container in _containers)
{
<option value="@container.TypeName">@container.Name</option>
}
</select>
</td>
</tr>
<tr>
<td>
<Label For="defaultAdminContainer" HelpText="Select the default admin container for the site" ResourceKey="DefaultAdminContainer">Default Admin Container: </Label>
</td>
<td>
<select id="defaultAdminContainer" class="form-select" @bind="@_admincontainertype">
<option value="-">&lt;@Localizer["Container.Select"]&gt;</option>
<option value="@Constants.DefaultAdminContainer">&lt;@Localizer["DefaultAdminContainer"]&gt;</option>
@foreach (var container in _containers)
{
<option value="@container.TypeName">@container.Name</option>
}
</select>
</td>
</tr>
</table>
</Section>
<Section Name="SMTP" Heading="SMTP Settings" ResourceKey="SMTPSettings">
<table class="table table-borderless">
<tr>
<td width="30%">&nbsp;</td>
<td>
<strong>@Localizer["Smtp.Required.EnableNotificationJob"]</strong><br />
</td>
</tr>
<tr>
<td>
<Label For="host" HelpText="Enter the host name of the SMTP server" ResourceKey="Host">Host: </Label>
</td>
<td>
<input id="host" class="form-control" @bind="@_smtphost" />
</td>
</tr>
<tr>
<td>
<Label For="port" HelpText="Enter the port number for the SMTP server. Please note this field is required if you provide a host name." ResourceKey="Port">Port: </Label>
</td>
<td>
<input id="port" class="form-control" @bind="@_smtpport" />
</td>
</tr>
<tr>
<td>
<Label For="enabledSSl" HelpText="Specify if SSL is required for your SMTP server" ResourceKey="UseSsl">SSL Enabled: </Label>
</td>
<td>
<select id="enabledSSl" class="form-select" @bind="@_smtpssl">
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</td>
</tr>
<tr>
<td>
<Label For="username" HelpText="Enter the username for your SMTP account" ResourceKey="SmptUsername">Username: </Label>
</td>
<td>
<input id="username" class="form-control" @bind="@_smtpusername" />
</td>
</tr>
<tr>
<td>
<Label For="password" HelpText="Enter the password for your SMTP account" ResourceKey="SmtpPassword">Password: </Label>
</td>
<td>
<input id="password" type="password" class="form-control" @bind="@_smtppassword" />
</td>
</tr>
<tr>
<td>
<Label For="sender" HelpText="Enter the email which emails will be sent from. Please note that this email address may need to be authorized with the SMTP server." ResourceKey="SmptSender">Email Sender: </Label>
</td>
<td>
<input id="sender" class="form-control" @bind="@_smtpsender" />
</td>
</tr>
</table>
<button type="button" class="btn btn-secondary" @onclick="SendEmail">@Localizer["Smtp.TestConfig"]</button>
<br /><br />
</Section>
<Section Name="PWA" Heading="Progressive Web Application Settings" ResourceKey="PWASettings">
<table class="table table-borderless">
<tr>
<td width="30%">
<Label For="isEnabled" HelpText="Select whether you would like this site to be available as a Progressive Web Application (PWA)" ResourceKey="EnablePWA">Is Enabled? </Label>
</td>
<td>
<select id="isEnabled" class="form-select" @bind="@_pwaisenabled">
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</td>
</tr>
<tr>
<td>
<Label For="appIcon" HelpText="Include an application icon for your PWA. It should be a PNG which is 192 X 192 pixels in dimension." ResourceKey="PwaApplicationIcon">App Icon: </Label>
</td>
<td>
<FileManager FileId="@_pwaappiconfileid" Filter="png" @ref="_pwaappiconfilemanager" />
</td>
</tr>
<tr>
<td>
<Label For="splashIcon" HelpText="Include a splash icon for your PWA. It should be a PNG which is 512 X 512 pixels in dimension." ResourceKey="PwaSplashIcon">Splash Icon: </Label>
</td>
<td>
<FileManager FileId="@_pwasplashiconfileid" Filter="png" @ref="_pwasplashiconfilemanager" />
</td>
</tr>
</table>
</Section>
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
<Section Name="TenantInformation" Heading="Tenant Information" ResourceKey="TenantInformation">
<table class="table table-borderless">
<tr>
<td width="30%">
<Label For="tenant" HelpText="The tenant for the site" ResourceKey="Tenant">Tenant: </Label>
</td>
<td>
<input id="tenant" class="form-control" @bind="@_tenant" readonly />
</td>
</tr>
<tr>
<td>
<Label For="database" HelpText="The database for the tenant" ResourceKey="Database">Database: </Label>
</td>
<td>
<input id="database" class="form-control" @bind="@_database" readonly />
</td>
</tr>
<tr>
<td>
<Label For="connectionstring" HelpText="The connection information for the database" ResourceKey="ConnectionString">Connection: </Label>
</td>
<td>
<textarea id="connectionstring" class="form-control" @bind="@_connectionstring" rows="2" readonly></textarea>
</td>
</tr>
</table>
</Section> </Section>
} <Section Name="SMTP" Heading="SMTP Settings" ResourceKey="SMTPSettings">
<br /> <div class="container">
<button type="button" class="btn btn-success" @onclick="SaveSite">@SharedLocalizer["Save"]</button> <div class="row mb-1 align-items-center">
<ActionDialog Header="Delete Site" Message="@Localizer["Confirm.DeleteSite"]" Action="Delete" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await DeleteSite())" ResourceKey="DeleteSite" /> <div class="col-sm-3">
<br /> </div>
<br /> <div class="col-sm-9">
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon" DeletedBy="@_deletedby" DeletedOn="@_deletedon"></AuditInfo> <strong>@Localizer["Smtp.Required.EnableNotificationJob"]</strong><br />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="host" HelpText="Enter the host name of the SMTP server" ResourceKey="Host">Host: </Label>
<div class="col-sm-9">
<input id="host" class="form-control" @bind="@_smtphost" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="port" HelpText="Enter the port number for the SMTP server. Please note this field is required if you provide a host name." ResourceKey="Port">Port: </Label>
<div class="col-sm-9">
<input id="port" class="form-control" @bind="@_smtpport" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="enabledSSl" HelpText="Specify if SSL is required for your SMTP server" ResourceKey="UseSsl">SSL Enabled: </Label>
<div class="col-sm-9">
<select id="enabledSSl" class="form-select" @bind="@_smtpssl" >
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="username" HelpText="Enter the username for your SMTP account" ResourceKey="SmptUsername">Username: </Label>
<div class="col-sm-9">
<input id="username" class="form-control" @bind="@_smtpusername" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="password" HelpText="Enter the password for your SMTP account" ResourceKey="SmtpPassword">Password: </Label>
<div class="col-sm-9">
<input id="password" type="password" class="form-control" @bind="@_smtppassword" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="sender" HelpText="Enter the email which emails will be sent from. Please note that this email address may need to be authorized with the SMTP server." ResourceKey="SmptSender">Email Sender: </Label>
<div class="col-sm-9">
<input id="sender" class="form-control" @bind="@_smtpsender" />
</div>
</div>
<button type="button" class="btn btn-secondary" @onclick="SendEmail">@Localizer["Smtp.TestConfig"]</button>
<br /><br />
</div>
</Section>
<Section Name="PWA" Heading="Progressive Web Application Settings" ResourceKey="PWASettings">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="isEnabled" HelpText="Select whether you would like this site to be available as a Progressive Web Application (PWA)" ResourceKey="EnablePWA">Is Enabled? </Label>
<div class="col-sm-9">
<select id="isEnabled" class="form-select" @bind="@_pwaisenabled" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="appIcon" HelpText="Include an application icon for your PWA. It should be a PNG which is 192 X 192 pixels in dimension." ResourceKey="PwaApplicationIcon">App Icon: </Label>
<div class="col-sm-9">
<FileManager FileId="@_pwaappiconfileid" Filter="png" @ref="_pwaappiconfilemanager" />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="splashIcon" HelpText="Include a splash icon for your PWA. It should be a PNG which is 512 X 512 pixels in dimension." ResourceKey="PwaSplashIcon">Splash Icon: </Label>
<div class="col-sm-9">
<FileManager FileId="@_pwasplashiconfileid" Filter="png" @ref="_pwasplashiconfilemanager" />
</div>
</div>
</div>
</Section>
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
<Section Name="TenantInformation" Heading="Tenant Information" ResourceKey="TenantInformation">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="tenant" HelpText="The tenant for the site" ResourceKey="Tenant">Tenant: </Label>
<div class="col-sm-9">
<input id="tenant" class="form-control" @bind="@_tenant" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="database" HelpText="The database for the tenant" ResourceKey="Database">Database: </Label>
<div class="col-sm-9">
<input id="database" class="form-control" @bind="@_database" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="connectionstring" HelpText="The connection information for the database" ResourceKey="ConnectionString">Connection: </Label>
<div class="col-sm-9">
<textarea id="connectionstring" class="form-control" @bind="@_connectionstring" rows="2" readonly></textarea>
</div>
</div>
</div>
</Section>
}
<br />
<button type="button" class="btn btn-success" @onclick="SaveSite">@SharedLocalizer["Save"]</button>
<ActionDialog Header="Delete Site" Message="@Localizer["Confirm.DeleteSite"]" Action="Delete" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await DeleteSite())" ResourceKey="DeleteSite" />
<br />
<br />
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon" DeletedBy="@_deletedby" DeletedOn="@_deletedon"></AuditInfo>
</form>
} }
@code { @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>();
@ -422,121 +388,130 @@
private async Task SaveSite() private async Task SaveSite()
{ {
try validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{ {
if (_name != string.Empty && _urls != string.Empty && _themetype != "-" && _containertype != "-") try
{ {
var unique = true; if (_name != string.Empty && _urls != string.Empty && _themetype != "-" && _containertype != "-")
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{ {
foreach (string name in _urls.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) var unique = true;
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{ {
if (_aliasList.Exists(item => item.Name == name && item.SiteId != PageState.Alias.SiteId && item.TenantId != PageState.Alias.TenantId)) foreach (string name in _urls.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{ {
unique = false; if (_aliasList.Exists(item => item.Name == name && item.SiteId != PageState.Alias.SiteId && item.TenantId != PageState.Alias.TenantId))
{
unique = false;
}
} }
} }
}
if (unique) if (unique)
{
var site = await SiteService.GetSiteAsync(PageState.Site.SiteId);
if (site != null)
{ {
bool refresh = (site.DefaultThemeType != _themetype || site.DefaultContainerType != _containertype); var site = await SiteService.GetSiteAsync(PageState.Site.SiteId);
if (site != null)
site.Name = _name;
site.AllowRegistration = (_allowregistration == null ? true : Boolean.Parse(_allowregistration));
site.IsDeleted = (_isdeleted == null ? true : Boolean.Parse(_isdeleted));
site.LogoFileId = null;
var logofileid = _logofilemanager.GetFileId();
if (logofileid != -1)
{ {
site.LogoFileId = logofileid; bool refresh = (site.DefaultThemeType != _themetype || site.DefaultContainerType != _containertype);
}
var faviconFieldId = _faviconfilemanager.GetFileId();
if (faviconFieldId != -1)
{
site.FaviconFileId = faviconFieldId;
}
site.DefaultThemeType = _themetype;
site.DefaultContainerType = _containertype;
site.AdminContainerType = _admincontainertype;
site.PwaIsEnabled = (_pwaisenabled == null ? true : Boolean.Parse(_pwaisenabled)); site.Name = _name;
var pwaappiconfileid = _pwaappiconfilemanager.GetFileId(); site.AllowRegistration = (_allowregistration == null ? true : Boolean.Parse(_allowregistration));
if (pwaappiconfileid != -1) site.IsDeleted = (_isdeleted == null ? true : Boolean.Parse(_isdeleted));
{
site.PwaAppIconFileId = pwaappiconfileid;
}
var pwasplashiconfileid = _pwasplashiconfilemanager.GetFileId();
if (pwasplashiconfileid != -1)
{
site.PwaSplashIconFileId = pwasplashiconfileid;
}
site = await SiteService.UpdateSiteAsync(site); site.LogoFileId = null;
var logofileid = _logofilemanager.GetFileId();
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId); if (logofileid != -1)
SettingService.SetSetting(settings, "SMTPHost", _smtphost);
SettingService.SetSetting(settings, "SMTPPort", _smtpport);
SettingService.SetSetting(settings, "SMTPSSL", _smtpssl);
SettingService.SetSetting(settings, "SMTPUsername", _smtpusername);
SettingService.SetSetting(settings, "SMTPPassword", _smtppassword);
SettingService.SetSetting(settings, "SMTPSender", _smtpsender);
await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId);
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
var names = _urls.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
foreach (Alias alias in _aliasList.Where(item => item.SiteId == site.SiteId && item.TenantId == site.TenantId).ToList())
{ {
if (!names.Contains(alias.Name)) site.LogoFileId = logofileid;
}
var faviconFieldId = _faviconfilemanager.GetFileId();
if (faviconFieldId != -1)
{
site.FaviconFileId = faviconFieldId;
}
site.DefaultThemeType = _themetype;
site.DefaultContainerType = _containertype;
site.AdminContainerType = _admincontainertype;
site.PwaIsEnabled = (_pwaisenabled == null ? true : Boolean.Parse(_pwaisenabled));
var pwaappiconfileid = _pwaappiconfilemanager.GetFileId();
if (pwaappiconfileid != -1)
{
site.PwaAppIconFileId = pwaappiconfileid;
}
var pwasplashiconfileid = _pwasplashiconfilemanager.GetFileId();
if (pwasplashiconfileid != -1)
{
site.PwaSplashIconFileId = pwasplashiconfileid;
}
site = await SiteService.UpdateSiteAsync(site);
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
SettingService.SetSetting(settings, "SMTPHost", _smtphost);
SettingService.SetSetting(settings, "SMTPPort", _smtpport);
SettingService.SetSetting(settings, "SMTPSSL", _smtpssl);
SettingService.SetSetting(settings, "SMTPUsername", _smtpusername);
SettingService.SetSetting(settings, "SMTPPassword", _smtppassword);
SettingService.SetSetting(settings, "SMTPSender", _smtpsender);
await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId);
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
var names = _urls.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
foreach (Alias alias in _aliasList.Where(item => item.SiteId == site.SiteId && item.TenantId == site.TenantId).ToList())
{ {
await AliasService.DeleteAliasAsync(alias.AliasId); if (!names.Contains(alias.Name))
{
await AliasService.DeleteAliasAsync(alias.AliasId);
}
}
foreach (string name in names)
{
if (!_aliasList.Exists(item => item.Name == name))
{
Alias alias = new Alias();
alias.Name = name;
alias.TenantId = site.TenantId;
alias.SiteId = site.SiteId;
await AliasService.AddAliasAsync(alias);
}
} }
} }
foreach (string name in names) await logger.LogInformation("Site Settings Saved {Site}", site);
if (refresh)
{ {
if (!_aliasList.Exists(item => item.Name == name)) NavigationManager.NavigateTo(NavigateUrl()); // refresh to show new theme or container
{ }
Alias alias = new Alias(); else
alias.Name = name; {
alias.TenantId = site.TenantId; AddModuleMessage(Localizer["Success.Settings.SaveSite"], MessageType.Success);
alias.SiteId = site.SiteId;
await AliasService.AddAliasAsync(alias);
}
} }
} }
}
await logger.LogInformation("Site Settings Saved {Site}", site); else
{
if (refresh) AddModuleMessage(Localizer["Message.Aliases.Taken"], MessageType.Warning);
{
NavigationManager.NavigateTo(NavigateUrl()); // refresh to show new theme or container
}
else
{
AddModuleMessage(Localizer["Success.Settings.SaveSite"], MessageType.Success);
}
} }
} }
else else
{ {
AddModuleMessage(Localizer["Message.Aliases.Taken"], MessageType.Warning); AddModuleMessage(Localizer["Message.Required.SiteName"], MessageType.Warning);
} }
} }
else catch (Exception ex)
{ {
AddModuleMessage(Localizer["Message.Required.SiteName"], MessageType.Warning); await logger.LogError(ex, "Error Saving Site {SiteId} {Error}", PageState.Site.SiteId, ex.Message);
AddModuleMessage(Localizer["Error.SaveSite"], MessageType.Error);
} }
} }
catch (Exception ex) else
{ {
await logger.LogError(ex, "Error Saving Site {SiteId} {Error}", PageState.Site.SiteId, ex.Message); AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
AddModuleMessage(Localizer["Error.SaveSite"], MessageType.Error);
} }
} }

View File

@ -19,158 +19,140 @@
} }
else else
{ {
<table class="table table-borderless"> <form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
<tr> <div class="container">
<td width="30%"> <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> @foreach (var theme in _themes)
</td>
<td>
<select id="defaultTheme" class="form-select" @onchange="(e => ThemeChanged(e))">
<option value="-">&lt;@Localizer["Theme.Select"]&gt;</option>
@foreach (var theme in _themes)
{
<option value="@theme.TypeName">@theme.Name</option>
}
</select>
</td>
</tr>
<tr>
<td>
<Label For="defaultContainer" HelpText="Select the default container for the site" ResourceKey="DefaultContainer">Default Container: </Label>
</td>
<td>
<select id="defaultContainer" class="form-select" @bind="@_containertype">
<option value="-">&lt;@Localizer["Container.Select"]&gt;</option>
@foreach (var container in _containers)
{
<option value="@container.TypeName">@container.Name</option>
}
</select>
</td>
</tr>
<tr>
<td>
<Label For="adminContainer" HelpText="Select the admin container for the site" ResourceKey="AdminContainer">Admin Container: </Label>
</td>
<td>
<select id="adminContainer" class="form-select" @bind="@_admincontainertype">
<option value="-">&lt;@Localizer["Container.Select"]&gt;</option>
<option value="">&lt;@Localizer["DefaultContainer.Admin"]&gt;</option>
@foreach (var container in _containers)
{
<option value="@container.TypeName">@container.Name</option>
}
</select>
</td>
</tr>
<tr>
<td>
<Label For="siteTemplate" HelpText="Select the site template" ResourceKey="SiteTemplate">Site Template: </Label>
</td>
<td>
<select id="siteTemplate" class="form-select" @bind="@_sitetemplatetype">
<option value="-">&lt;@Localizer["SiteTemplate.Select"]&gt;</option>
@foreach (SiteTemplate siteTemplate in _siteTemplates)
{
<option value="@siteTemplate.TypeName">@siteTemplate.Name</option>
}
</select>
</td>
</tr>
<tr>
<td>
<Label For="tenant" HelpText="Select the tenant for the site" ResourceKey="Tenant">Tenant: </Label>
</td>
<td>
<select id="tenant" class="form-select" @onchange="(e => TenantChanged(e))">
<option value="-">&lt;@Localizer["Tenant.Select"]&gt;</option>
<option value="+">&lt;@Localizer["Tenant.Add"]&gt;</option>
@foreach (Tenant tenant in _tenants)
{
<option value="@tenant.TenantId">@tenant.Name</option>
}
</select>
</td>
</tr>
@if (_tenantid == "+")
{
<tr>
<td colspan="2">
<hr class="app-rule" />
</td>
</tr>
<tr>
<td>
<Label For="name" HelpText="Enter the name for the tenant" ResourceKey="TenantName">Tenant Name: </Label>
</td>
<td>
<input id="name" class="form-control" @bind="@_tenantName" />
</td>
</tr>
<tr>
<td>
<Label For="databaseType" HelpText="Select the database type for the tenant" ResourceKey="DatabaseType">Database Type: </Label>
</td>
<td>
<select id="databaseType" class="form-select" value="@_databaseName" @onchange="(e => DatabaseChanged(e))">
@foreach (var database in _databases)
{ {
if (database.IsDefault) <option value="@theme.TypeName">@theme.Name</option>
{
<option value="@database.Name" selected>@Localizer[@database.Name]</option>
}
else
{
<option value="@database.Name">@Localizer[@database.Name]</option>
}
} }
</select> </select>
</td> </div>
</tr> </div>
if (_databaseConfigType != null) <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="defaultContainer" HelpText="Select the default container for the site" ResourceKey="DefaultContainer">Default Container: </Label>
<div class="col-sm-9">
<select id="defaultContainer" class="form-select" @bind="@_containertype" required>
<option value="-">&lt;@Localizer["Container.Select"]&gt;</option>
@foreach (var container in _containers)
{
<option value="@container.TypeName">@container.Name</option>
}
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="adminContainer" HelpText="Select the admin container for the site" ResourceKey="AdminContainer">Admin Container: </Label>
<div class="col-sm-9">
<select id="adminContainer" class="form-select" @bind="@_admincontainertype" required>
<option value="-">&lt;@Localizer["Container.Select"]&gt;</option>
<option value="">&lt;@Localizer["DefaultContainer.Admin"]&gt;</option>
@foreach (var container in _containers)
{
<option value="@container.TypeName">@container.Name</option>
}
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="siteTemplate" HelpText="Select the site template" ResourceKey="SiteTemplate">Site Template: </Label>
<div class="col-sm-9">
<select id="siteTemplate" class="form-select" @bind="@_sitetemplatetype" required>
<option value="-">&lt;@Localizer["SiteTemplate.Select"]&gt;</option>
@foreach (SiteTemplate siteTemplate in _siteTemplates)
{
<option value="@siteTemplate.TypeName">@siteTemplate.Name</option>
}
</select>
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="tenant" HelpText="Select the tenant for the site" ResourceKey="Tenant">Tenant: </Label>
<div class="col-sm-9">
<select id="tenant" class="form-select" @onchange="(e => TenantChanged(e))" required>
<option value="-">&lt;@Localizer["Tenant.Select"]&gt;</option>
<option value="+">&lt;@Localizer["Tenant.Add"]&gt;</option>
@foreach (Tenant tenant in _tenants)
{
<option value="@tenant.TenantId">@tenant.Name</option>
}
</select>
</div>
</div>
@if (_tenantid == "+")
{ {
@DatabaseConfigComponent; <div class="row mb-1 align-items-center">
<hr class="app-rule" />
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="name" HelpText="Enter the name for the tenant" ResourceKey="TenantName">Tenant Name: </Label>
<div class="col-sm-9">
<input id="name" class="form-control" @bind="@_tenantName" maxlength="100" required />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="databaseType" HelpText="Select the database type for the tenant" ResourceKey="DatabaseType">Database Type: </Label>
<div class="col-sm-9">
<select id="databaseType" class="form-select" value="@_databaseName" @onchange="(e => DatabaseChanged(e))" required>
@foreach (var database in _databases)
{
if (database.IsDefault)
{
<option value="@database.Name" selected>@Localizer[@database.Name]</option>
}
else
{
<option value="@database.Name">@Localizer[@database.Name]</option>
}
}
</select>
</div>
</div>
if (_databaseConfigType != null)
{
@DatabaseConfigComponent;
}
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="hostUsername" HelpText="Enter the username of the host for this site" ResourceKey="HostUsername">Host Username:</Label>
<div class="col-sm-9">
<input id="hostUsername" class="form-control" @bind="@_hostUserName" readonly />
</div>
</div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="hostPassword" HelpText="Enter the password for the host of this site" ResourceKey="HostPassword">Host Password:</Label>
<div class="col-sm-9">
<input id="hostPassword" type="password" class="form-control" @bind="@_hostpassword" required />
</div>
</div>
} }
<tr> </div>
<td> <br />
<Label For="hostUsername" HelpText="Enter the username of the host for this site" ResourceKey="HostUsername">Host Username:</Label> <br />
</td> <button type="button" class="btn btn-success" @onclick="SaveSite">@SharedLocalizer["Save"]</button>
<td> <NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
<input id="hostUsername" class="form-control" @bind="@_hostUserName" readonly /> </form>
</td>
</tr>
<tr>
<td>
<Label For="hostPassword" HelpText="Enter the password for the host of this site" ResourceKey="HostPassword">Host Password:</Label>
</td>
<td>
<input id="hostPassword" type="password" class="form-control" @bind="@_hostpassword" />
</td>
</tr>
}
</table>
<button type="button" class="btn btn-success" @onclick="SaveSite">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
} }
@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;
@ -274,111 +256,120 @@ else
private async Task SaveSite() private async Task SaveSite()
{ {
if (_tenantid != "-" && _name != string.Empty && _urls != string.Empty && _themetype != "-" && _containertype != "-" && _sitetemplatetype != "-") validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{ {
var duplicates = new List<string>(); if (_tenantid != "-" && _name != string.Empty && _urls != string.Empty && _themetype != "-" && _containertype != "-" && _sitetemplatetype != "-")
var aliases = await AliasService.GetAliasesAsync();
foreach (string name in _urls.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{ {
if (aliases.Exists(item => item.Name == name)) var duplicates = new List<string>();
var aliases = await AliasService.GetAliasesAsync();
foreach (string name in _urls.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{ {
duplicates.Add(name); if (aliases.Exists(item => item.Name == name))
}
}
if (duplicates.Count == 0)
{
InstallConfig config = new InstallConfig();
if (_tenantid == "+")
{
if (!string.IsNullOrEmpty(_tenantName) && _tenants.FirstOrDefault(item => item.Name == _tenantName) == null)
{ {
// validate host credentials duplicates.Add(name);
var user = new User(); }
user.SiteId = PageState.Site.SiteId; }
user.Username = UserNames.Host;
user.Password = _hostpassword;
user = await UserService.LoginUserAsync(user, false, false);
if (user.IsAuthenticated)
{
var connectionString = String.Empty;
if (_databaseConfig is IDatabaseConfigControl databaseConfigControl)
{
connectionString = databaseConfigControl.GetConnectionString();
}
var database = _databases.SingleOrDefault(d => d.Name == _databaseName);
if (connectionString != "") if (duplicates.Count == 0)
{
InstallConfig config = new InstallConfig();
if (_tenantid == "+")
{
if (!string.IsNullOrEmpty(_tenantName) && _tenants.FirstOrDefault(item => item.Name == _tenantName) == null)
{
// validate host credentials
var user = new User();
user.SiteId = PageState.Site.SiteId;
user.Username = UserNames.Host;
user.Password = _hostpassword;
user = await UserService.LoginUserAsync(user, false, false);
if (user.IsAuthenticated)
{ {
config.TenantName = _tenantName; var connectionString = String.Empty;
config.DatabaseType = database.DBType; if (_databaseConfig is IDatabaseConfigControl databaseConfigControl)
config.ConnectionString = connectionString; {
config.HostEmail = user.Email; connectionString = databaseConfigControl.GetConnectionString();
config.HostPassword = _hostpassword; }
config.HostName = user.DisplayName; var database = _databases.SingleOrDefault(d => d.Name == _databaseName);
config.IsNewTenant = true;
if (connectionString != "")
{
config.TenantName = _tenantName;
config.DatabaseType = database.DBType;
config.ConnectionString = connectionString;
config.HostEmail = user.Email;
config.HostPassword = _hostpassword;
config.HostName = user.DisplayName;
config.IsNewTenant = true;
}
else
{
AddModuleMessage(Localizer["Error.Required.ServerDatabase"], MessageType.Error);
}
} }
else else
{ {
AddModuleMessage(Localizer["Error.Required.ServerDatabase"], MessageType.Error); AddModuleMessage(Localizer["Error.InvalidPassword"], MessageType.Error);
} }
} }
else else
{ {
AddModuleMessage(Localizer["Error.InvalidPassword"], MessageType.Error); AddModuleMessage(Localizer["Error.TenantName.Exists"], MessageType.Error);
} }
} }
else else
{ {
AddModuleMessage(Localizer["Error.TenantName.Exists"], MessageType.Error); var tenant = _tenants.FirstOrDefault(item => item.TenantId == int.Parse(_tenantid));
if (tenant != null)
{
config.TenantName = tenant.Name;
config.DatabaseType = tenant.DBType;
config.ConnectionString = tenant.DBConnectionString;
config.IsNewTenant = false;
}
}
if (!string.IsNullOrEmpty(config.TenantName))
{
config.SiteName = _name;
config.Aliases = _urls;
config.DefaultTheme = _themetype;
config.DefaultContainer = _containertype;
config.DefaultAdminContainer = _admincontainertype;
config.SiteTemplate = _sitetemplatetype;
ShowProgressIndicator();
var installation = await InstallationService.Install(config);
if (installation.Success)
{
var aliasname = config.Aliases.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)[0];
var uri = new Uri(NavigationManager.Uri);
NavigationManager.NavigateTo(uri.Scheme + "://" + aliasname, true);
}
else
{
await logger.LogError("Error Creating Site {Error}", installation.Message);
AddModuleMessage(installation.Message, MessageType.Error);
}
} }
} }
else else
{ {
var tenant = _tenants.FirstOrDefault(item => item.TenantId == int.Parse(_tenantid)); AddModuleMessage(string.Format(Localizer["Message.SiteName.InUse"], string.Join(", ", duplicates.ToArray())), MessageType.Warning);
if (tenant != null)
{
config.TenantName = tenant.Name;
config.DatabaseType = tenant.DBType;
config.ConnectionString = tenant.DBConnectionString;
config.IsNewTenant = false;
}
}
if (!string.IsNullOrEmpty(config.TenantName))
{
config.SiteName = _name;
config.Aliases = _urls;
config.DefaultTheme = _themetype;
config.DefaultContainer = _containertype;
config.DefaultAdminContainer = _admincontainertype;
config.SiteTemplate = _sitetemplatetype;
ShowProgressIndicator();
var installation = await InstallationService.Install(config);
if (installation.Success)
{
var aliasname = config.Aliases.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)[0];
var uri = new Uri(NavigationManager.Uri);
NavigationManager.NavigateTo(uri.Scheme + "://" + aliasname, true);
}
else
{
await logger.LogError("Error Creating Site {Error}", installation.Message);
AddModuleMessage(installation.Message, MessageType.Error);
}
} }
} }
else else
{ {
AddModuleMessage(string.Format(Localizer["Message.SiteName.InUse"], string.Join(", ", duplicates.ToArray())), MessageType.Warning); AddModuleMessage(Localizer["Message.Required.Tenant"], MessageType.Warning);
} }
} }
else else
{ {
AddModuleMessage(Localizer["Message.Required.Tenant"], MessageType.Warning); AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
} }
} }
} }

View File

@ -13,12 +13,11 @@
} }
else else
{ {
<table class="table table-borderless"> <div class="container">
<tr>
<td width="30%"> <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="tenant" class="form-select" value="@_tenantid" @onchange="(e => TenantChanged(e))">
<option value="-1">&lt;@Localizer["Tenant.Select"]&gt;</option> <option value="-1">&lt;@Localizer["Tenant.Select"]&gt;</option>
@foreach (Tenant tenant in _tenants) @foreach (Tenant tenant in _tenants)
@ -26,42 +25,61 @@ else
<option value="@tenant.TenantId">@tenant.Name</option> <option value="@tenant.TenantId">@tenant.Name</option>
} }
</select> </select>
</td> </div>
</tr> </div>
@if (_tenantid != "-1") @if (_tenantid != "-1")
{ {
<tr> <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>
<Label For="database" HelpText="The database for the tenant" ResourceKey="Database">Database: </Label> <div class="col-sm-9">
</td>
<td>
<input id="database" class="form-control" @bind="@_database" readonly /> <input id="database" class="form-control" @bind="@_database" readonly />
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="connectionstring" HelpText="The connection information for the database" ResourceKey="ConnectionString">Connection: </Label>
<Label For="connectionstring" HelpText="The connection information for the database" ResourceKey="ConnectionString">Connection: </Label> <div class="col-sm-9">
</td>
<td>
<textarea id="connectionstring" class="form-control" @bind="@_connectionstring" rows="2" readonly></textarea> <textarea id="connectionstring" class="form-control" @bind="@_connectionstring" rows="2" readonly></textarea>
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="sqlQeury" HelpText="Enter the query for the SQL server" ResourceKey="SqlQuery">SQL Query: </Label>
<Label For="sqlQeury" HelpText="Enter the query for the SQL server" ResourceKey="SqlQuery">SQL Query: </Label> <div class="col-sm-9">
</td> <textarea id="sqlQeury" class="form-control" @bind="@_sql" rows="3"></textarea>
<td> </div>
<textarea id="sqlQeury" class="form-control" @bind="@_sql" rows="5"></textarea> </div>
</td>
</tr>
} }
</table> </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 />
} }
} }
@ -71,7 +89,7 @@ else
private string _database = string.Empty; private string _database = string.Empty;
private string _connectionstring = 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;
@ -118,7 +136,7 @@ else
{ {
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); AddModuleMessage(Localizer["Success.QueryExecuted"], MessageType.Success);
} }
else else
@ -132,44 +150,4 @@ else
AddModuleMessage(ex.Message, MessageType.Error); AddModuleMessage(ex.Message, MessageType.Error);
} }
} }
private string DisplayResults(List<Dictionary<string, string>> results)
{
var table = string.Empty;
foreach (Dictionary<string, string> item in results)
{
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["Return.NoResult"];
}
return table;
}
} }

View File

@ -7,105 +7,79 @@
<TabStrip> <TabStrip>
<TabPanel Name="Info" Heading="Info" ResourceKey="Info"> <TabPanel Name="Info" Heading="Info" ResourceKey="Info">
<table class="table table-borderless"> <div class="container">
<tr> <div class="row mb-1 align-items-center">
<td width="30%"> <Label Class="col-sm-3" For="version" HelpText="Framework Version" ResourceKey="FrameworkVersion">Framework Version: </Label>
<Label For="version" HelpText="Framework Version" ResourceKey="FrameworkVersion">Framework Version: </Label> <div class="col-sm-9">
</td>
<td>
<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="clrversion" HelpText="Common Language Runtime Version" ResourceKey="CLRVersion">CLR Version: </Label> <div class="col-sm-9">
</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>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="installationid" HelpText="The Unique Identifier For Your Installation" ResourceKey="InstallationId">Installation ID: </Label>
<Label For="installationid" HelpText="The Unique Identifier For Your Installation" ResourceKey="InstallationId">Installation ID: </Label> <div class="col-sm-9">
</td>
<td>
<input id="installationid" class="form-control" @bind="@_installationid" readonly /> <input id="installationid" class="form-control" @bind="@_installationid" readonly />
</td> </div>
</tr> </div>
<tr> </div>
<td>&nbsp;</td>
<td>
<br /><input type="checkbox" @onchange="(e => RegisterChecked(e))" /> @Localizer["Register"]
</td>
</tr>
</table>
<br /><br /> <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>
<TabPanel Name="Options" Heading="Options" ResourceKey="Options"> <TabPanel Name="Options" Heading="Options" ResourceKey="Options">
<table class="table table-borderless"> <div class="container">
<tr> <div class="row mb-1 align-items-center">
<td width="30%"> <Label Class="col-sm-3" For="runtime" HelpText="Blazor Runtime (Server or WebAssembly)" ResourceKey="BlazorRuntime">Blazor Runtime: </Label>
<Label For="runtime" HelpText="Blazor Runtime (Server or WebAssembly)" ResourceKey="BlazorRuntime">Blazor Runtime: </Label> <div class="col-sm-9">
</td>
<td>
<select id="runtime" class="form-select" @bind="@_runtime"> <select id="runtime" class="form-select" @bind="@_runtime">
<option value="Server">@Localizer["Server"]</option> <option value="Server">@Localizer["Server"]</option>
<option value="WebAssembly">@Localizer["WebAssembly"]</option> <option value="WebAssembly">@Localizer["WebAssembly"]</option>
</select> </select>
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="rendermode" HelpText="Blazor Server Render Mode" ResourceKey="RenderMode">Render Mode: </Label>
<Label For="rendermode" HelpText="Blazor Server Render Mode" ResourceKey="RenderMode">Render Mode: </Label> <div class="col-sm-9">
</td>
<td>
<select id="rendermode" class="form-select" @bind="@_rendermode"> <select id="rendermode" class="form-select" @bind="@_rendermode">
<option value="Server">@Localizer["Server"]</option> <option value="Server">@Localizer["Server"]</option>
<option value="ServerPrerendered">@Localizer["ServerPrerendered"]</option> <option value="ServerPrerendered">@Localizer["ServerPrerendered"]</option>
</select> </select>
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <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>
<Label 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">
</td>
<td>
<select id="detailederrors" class="form-select" @bind="@_detailederrors"> <select id="detailederrors" class="form-select" @bind="@_detailederrors">
<option value="true">@SharedLocalizer["True"]</option> <option value="true">@SharedLocalizer["True"]</option>
<option value="false">@SharedLocalizer["False"]</option> <option value="false">@SharedLocalizer["False"]</option>
</select> </select>
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <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>
<Label 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">
</td>
<td>
<select id="logginglevel" class="form-select" @bind="@_logginglevel"> <select id="logginglevel" class="form-select" @bind="@_logginglevel">
<option value="Trace">@Localizer["Trace"]</option> <option value="Trace">@Localizer["Trace"]</option>
<option value="Debug">@Localizer["Debug"]</option> <option value="Debug">@Localizer["Debug"]</option>
@ -114,31 +88,27 @@
<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>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="swagger" HelpText="Specify If Swagger Is Enabled For Your Server API" ResourceKey="Swagger">Swagger Enabled? </Label>
<Label For="swagger" HelpText="Specify If Swagger Is Enabled For Your Server API" ResourceKey="Swagger">Swagger Enabled? </Label> <div class="col-sm-9">
</td>
<td>
<select id="swagger" class="form-select" @bind="@_swagger"> <select id="swagger" class="form-select" @bind="@_swagger">
<option value="true">@SharedLocalizer["True"]</option> <option value="true">@SharedLocalizer["True"]</option>
<option value="false">@SharedLocalizer["False"]</option> <option value="false">@SharedLocalizer["False"]</option>
</select> </select>
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="packageservice" HelpText="Specify If The Package Service Is Enabled For Installing Modules, Themes, And Translations" ResourceKey="PackageService">Enable Package Service? </Label>
<Label 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">
</td>
<td>
<select id="packageservice" class="form-select" @bind="@_packageservice"> <select id="packageservice" class="form-select" @bind="@_packageservice">
<option value="true">@SharedLocalizer["True"]</option> <option value="true">@SharedLocalizer["True"]</option>
<option value="false">@SharedLocalizer["False"]</option> <option value="false">@SharedLocalizer["False"]</option>
</select> </select>
</td> </div>
</tr> </div>
</table> </div>
<br /><br /> <br /><br />
<button type="button" class="btn btn-success" @onclick="SaveConfig">@SharedLocalizer["Save"]</button>&nbsp; <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; <a class="btn btn-primary" href="swagger/index.html" target="_new">@Localizer["Access.ApiFramework"]</a>&nbsp;
@ -220,20 +190,4 @@
await logger.LogError(ex, "Error Restarting Application"); await logger.LogError(ex, "Error Restarting Application");
} }
} }
private async Task RegisterChecked(ChangeEventArgs e)
{
try
{
if ((bool)e.Value)
{
await InstallationService.RegisterAsync(PageState.User.Email);
AddModuleMessage(Localizer["Success.Register"], MessageType.Success);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error On Register");
}
}
} }

View File

@ -9,19 +9,19 @@
<TabStrip> <TabStrip>
<TabPanel Name="Download" ResourceKey="Download"> <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> <div class="row justify-content-center mb-3">
<div class="col-sm-6">
<table class="table table-borderless" style=" margin: auto; width: 50% !important;"> <div class="input-group">
<tr> <select id="price" class="form-select custom-select" @onchange="(e => PriceChanged(e))">
<td> <option value="free">@SharedLocalizer["Free"]</option>
<option value="paid">@SharedLocalizer["Paid"]</option>
</select>
<input id="search" class="form-control" placeholder="@SharedLocalizer["Search.Hint"]" @bind="@_search" /> <input id="search" class="form-control" placeholder="@SharedLocalizer["Search.Hint"]" @bind="@_search" />
</td> <button type="button" class="btn btn-primary" @onclick="Search">@SharedLocalizer["Search"]</button>
<td>
<button type="button" class="btn btn-primary" @onclick="Search">@SharedLocalizer["Search"]</button>&nbsp;
<button type="button" class="btn btn-secondary" @onclick="Reset">@SharedLocalizer["Reset"]</button> <button type="button" class="btn btn-secondary" @onclick="Reset">@SharedLocalizer["Reset"]</button>
</td> </div>
</tr> </div>
</table> </div>
@if (_packages != null) @if (_packages != null)
{ {
@ -32,10 +32,26 @@
<td> <td>
<h3 style="display: inline;"><a href="@context.ProductUrl" target="_new">@context.Name</a></h3>&nbsp;&nbsp;@SharedLocalizer["Search.By"]:&nbsp;&nbsp;<strong><a href="@context.OwnerUrl" target="new">@context.Owner</a></strong><br /> <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 /> @(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>&nbsp;&nbsp;|&nbsp;&nbsp;@SharedLocalizer["Search.Source"]: <strong>@context.PackageUrl</strong> <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>
<td style="vertical-align: middle;"> <td style="width: 1px; vertical-align: middle;">
<button type="button" class="btn btn-primary" @onclick=@(async () => await DownloadTheme(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button> @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>
@ -50,25 +66,62 @@
} }
</TabPanel> </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>
@if (_productname != "")
{
<div class="app-actiondialog">
<div class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">@SharedLocalizer["Review License Terms"]</h5>
<button type="button" class="btn-close" aria-label="Close" @onclick="HideModal"></button>
</div>
<div class="modal-body">
<p style="height: 200px; overflow-y: scroll;">
<h3>@_productname</h3>
@if (!string.IsNullOrEmpty(_license))
{
@((MarkupString)_license)
}
else
{
@SharedLocalizer["License Not Specified"]
}
</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-success" @onclick="DownloadPackage">@SharedLocalizer["Accept"]</button>
<button type="button" class="btn btn-secondary" @onclick="HideModal">@SharedLocalizer["Cancel"]</button>
</div>
</div>
</div>
</div>
</div>
}
<button type="button" class="btn btn-success" @onclick="InstallThemes">@SharedLocalizer["Install"]</button> <button type="button" class="btn btn-success" @onclick="InstallThemes">@SharedLocalizer["Install"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink> <NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
@code { @code {
private List<Package> _packages; private List<Package> _packages;
private string _price = "free";
private string _search = ""; 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;
@ -88,7 +141,7 @@
private async Task LoadThemes() private async Task LoadThemes()
{ {
var themes = await ThemeService.GetThemesAsync(); var themes = await ThemeService.GetThemesAsync();
_packages = await PackageService.GetPackagesAsync("theme", _search); _packages = await PackageService.GetPackagesAsync("theme", _search, _price, "");
if (_packages != null) if (_packages != null)
{ {
@ -102,6 +155,21 @@
} }
} }
private async void PriceChanged(ChangeEventArgs e)
{
try
{
_price = (string)e.Value;
_search = "";
await LoadThemes();
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error On PriceChanged");
}
}
private async Task Search() private async Task Search()
{ {
try try
@ -127,6 +195,55 @@
} }
} }
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);
}
}
private async Task InstallThemes() private async Task InstallThemes()
{ {
try try
@ -139,20 +256,4 @@
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["Success.Theme.Download"], MessageType.Success);
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Downloading Module {ThemeName} {Version}", packageid, version);
AddModuleMessage(Localizer["Error.Theme.Download"], MessageType.Error);
}
}
} }

View File

@ -11,66 +11,56 @@
@if (_templates != null) @if (_templates != null)
{ {
<table class="table table-borderless"> <div class="container">
<tr> <div class="row mb-1 align-items-center">
<td width="30%"> <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> <input id="owner" class="form-control" @bind="@_owner" />
<td> </div>
<input id="owner" class="form-control" @bind="@_owner" /> </div>
</td> <div class="row mb-1 align-items-center">
</tr> <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>
<tr> <div class="col-sm-9">
<td> <input id="module" class="form-control" @bind="@_theme" />
<Label For="module" HelpText="Enter a name for this theme. It should not contain spaces or punctuation." ResourceKey="ThemeName">Theme Name: </Label> </div>
</td> </div>
<td> <div class="row mb-1 align-items-center">
<input id="module" class="form-control" @bind="@_theme" /> <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>
</td> <div class="col-sm-9">
</tr> <select id="template" class="form-select" @onchange="(e => TemplateChanged(e))">
<tr> <option value="-">&lt;@Localizer["Template.Select"]&gt;</option>
<td> @foreach (Template template in _templates)
<Label For="template" HelpText="Select a theme template. Templates are located in the wwwroot/Themes/Templates folder on the server." ResourceKey="Template">Template: </Label> {
</td> <option value="@template.Name">@template.Title</option>
<td> }
<select id="template" class="form-select" @onchange="(e => TemplateChanged(e))"> </select>
<option value="-">&lt;@Localizer["Template.Select"]&gt;</option> </div>
@foreach (Template template in _templates) </div>
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="reference" HelpText="Select a framework reference version" ResourceKey="FrameworkReference">Framework Reference: </Label>
<div class="col-sm-9">
<select id="reference" class="form-select" @bind="@_reference">
@foreach (string version in _versions)
{
if (Version.Parse(version).CompareTo(Version.Parse(_minversion)) >= 0)
{ {
<option value="@template.Name">@template.Title</option> <option value="@(version)">@(version)</option>
} }
</select> }
</td> <option value="local">@SharedLocalizer["LocalVersion"]</option>
</tr> </select>
<tr> </div>
<td> </div>
<Label For="reference" HelpText="Select a framework reference version" ResourceKey="FrameworkReference">Framework Reference: </Label> @if (!string.IsNullOrEmpty(_location)) {
</td> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="location" HelpText="Location where the theme will be created" ResourceKey="Location">Location: </Label>
<select id="reference" class="form-select" @bind="@_reference"> <div class="col-sm-9">
@foreach (string version in _versions) <input id="module" class="form-control" @bind="@_location" readonly />
{ </div>
if (Version.Parse(version).CompareTo(Version.Parse(_minversion)) >= 0) </div>
{ }
<option value="@(version)">@(version)</option> </div>
} <br />
}
<option value="local">@SharedLocalizer["LocalVersion"]</option>
</select>
</td>
</tr>
@if (!string.IsNullOrEmpty(_location))
{
<tr>
<td>
<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 />
</td>
</tr>
}
</table>
<button type="button" class="btn btn-success" @onclick="CreateTheme">@Localizer["Theme.Create"]</button> <button type="button" class="btn btn-success" @onclick="CreateTheme">@Localizer["Theme.Create"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink> <NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
} }

View File

@ -21,8 +21,9 @@ 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 scope="col">@SharedLocalizer["Name"]</th> <th>@SharedLocalizer["Name"]</th>
<th scope="col">@SharedLocalizer["Version"]</th> <th>@SharedLocalizer["Version"]</th>
<th>@SharedLocalizer["Expires"]</th>
<th>&nbsp;</th> <th>&nbsp;</th>
</Header> </Header>
<Row> <Row>
@ -35,11 +36,14 @@ else
</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))>@SharedLocalizer["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>
</Row> </Row>
@ -69,10 +73,31 @@ else
} }
} }
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)

View File

@ -6,64 +6,50 @@
@inject IStringLocalizer<View> Localizer @inject IStringLocalizer<View> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer @inject IStringLocalizer<SharedResources> SharedLocalizer
<table class="table table-borderless"> <div class="container">
<tr> <div class="row mb-1 align-items-center">
<td width="30%"> <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()">@SharedLocalizer["Cancel"]</NavLink> <NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
@code { @code {

View File

@ -22,16 +22,14 @@
</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">@SharedLocalizer["Upgrade"]</button> <button type="button" class="btn btn-success" @onclick="Upgrade">@SharedLocalizer["Upgrade"]</button>
</TabPanel> </TabPanel>
</TabStrip> </TabStrip>
@ -46,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();
@ -73,7 +71,7 @@
AddModuleMessage(Localizer["Info.Upgrade.Wait"], 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(), 20); await interop.RedirectBrowser(NavigateUrl(), 10);
await InstallationService.Upgrade(); await InstallationService.Upgrade();
} }
catch (Exception ex) catch (Exception ex)

View File

@ -8,32 +8,27 @@
@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 width="30%"> <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> <input id="to" class="form-control" @bind="@username" />
<td> </div> >
<input id="to" class="form-control" @bind="@username" /> </div>
</td> <div class="row mb-1 align-items-center">
</tr> <Label Class="col-sm-3" For="subject" HelpText="Enter the subject of the message" ResourceKey="Subject">Subject: </Label>
<tr> <div class="col-sm-9">
<td> <input id="subject" class="form-control" @bind="@subject" />
<Label For="subject" HelpText="Enter the subject of the message" ResourceKey="Subject">Subject: </Label> </div>
</td> </div>
<td> <div class="row mb-1 align-items-center">
<input id="subject" class="form-control" @bind="@subject" /> <Label Class="col-sm-3" For="message" HelpText="Enter the message" ResourceKey="Message">Message: </Label>
</td> <div class="col-sm-9">
</tr> <textarea id="message" class="form-control" @bind="@body" rows="5" />
<tr> </div>
<td> </div>
<Label For="message" HelpText="Enter the message" ResourceKey="Message">Message: </Label> </div>
</td> <br/>
<td>
<textarea id="message" class="form-control" @bind="@body" rows="5" />
</td>
</tr>
</table>
<button type="button" class="btn btn-primary" @onclick="Send">@SharedLocalizer["Send"]</button> <button type="button" class="btn btn-primary" @onclick="Send">@SharedLocalizer["Send"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink> <NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
} }

View File

@ -6,12 +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 @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
{ {
@ -19,58 +20,47 @@ 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 width="30%"> <Label Class="col-sm-3" For="username" HelpText="Your username. Note that this field can not be modified." ResourceKey="Username"></Label>
<Label For="username" HelpText="Your username. Note that this field can not be modified." ResourceKey="Username"></Label> <div class="col-sm-9">
</td>
<td>
<input id="username" class="form-control" @bind="@username" readonly /> <input id="username" 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="password" HelpText="If you wish to change your password you can enter it here. Please choose a sufficiently secure password." ResourceKey="Password"></Label>
<Label For="password" HelpText="If you wish to change your password you can enter it here. Please choose a sufficiently secure password." ResourceKey="Password"></Label> <div class="col-sm-9">
</td> <input id="password" type="password" class="form-control" @bind="@password" autocomplete="new-password" />
<td> </div>
<input id ="password" type="password" class="form-control" @bind="@password" autocomplete="new-password" /> </div>
</td> <div class="row mb-1 align-items-center">
</tr> <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>
<tr> <div class="col-sm-9">
<td>
<Label For="confirm" HelpText="If you are changing your password you must enter it again to confirm it matches" ResourceKey="Confirm"></Label>
</td>
<td>
<input id="confirm" type="password" class="form-control" @bind="@confirm" autocomplete="new-password" /> <input id="confirm" type="password" class="form-control" @bind="@confirm" autocomplete="new-password" />
</td> </div>
</tr> </div>
<tr> <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>
<Label For="email" HelpText="Your email address where you wish to receive notifications" ResourceKey="Email"></Label> <div class="col-sm-9">
</td>
<td>
<input id="email" class="form-control" @bind="@email" /> <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="displayname" HelpText="Your full name" ResourceKey="DisplayName"></Label> <div class="col-sm-9">
</td>
<td>
<input id="displayname" class="form-control" @bind="@displayname" /> <input id="displayname" class="form-control" @bind="@displayname" />
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="@photofileid.ToString()" HelpText="A photo of yourself" ResourceKey="Photo"></Label>
<Label For="@photofileid.ToString()" HelpText="A photo of yourself" ResourceKey="Photo"></Label> <div class="col-sm-9">
</td> <FileManager FileId="@photofileid" Filter="@Constants.ImageFiles" ShowFolders="false" ShowFiles="true" UploadMultiple="false" FolderId="@folderid" @ref="filemanager" />
<td> </div>
<FileManager FileId="@photofileid" @ref="filemanager" /> </div>
</td> </div>
</tr> <br />
</table>
<button type="button" class="btn btn-success" @onclick="Save">@SharedLocalizer["Save"]</button> <button type="button" class="btn btn-success" @onclick="Save">@SharedLocalizer["Save"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button> <button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
} }
@ -78,58 +68,56 @@ else
<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">
@foreach (Profile profile in profiles) <div class="row mb-1 align-items-center">
{ @foreach (Profile profile in profiles)
var p = profile;
if (!p.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
{ {
if (p.Category != category) var p = profile;
if (!p.IsPrivate || UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
{ {
<tr> if (p.Category != category)
<th colspan="2" style="text-align: center;"> {
<div class="col text-center pb-2">
@p.Category @p.Category
</th> </div>
</tr> category = p.Category;
category = p.Category; }
} <div class="row mb-1 align-items-center">
<tr> <Label Class="col-sm-3" For="@p.Name" HelpText="@p.Description">@p.Title</Label>
<td width="30%"> <div class="col-sm-9">
<Label For="@p.Name" HelpText="@p.Description">@p.Title</Label> @if (!string.IsNullOrEmpty(p.Options))
</td>
<td>
@if (!string.IsNullOrEmpty(p.Options))
{
<select id="@p.Name" class="form-select" @onchange="@(e => ProfileChanged(e, p.Name))">
@foreach (var option in p.Options.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
@if (GetProfileValue(p.Name, "") == option || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == option))
{
<option value="@option" selected>@option</option>
}
else
{
<option value="@option">@option</option>
}
}
</select>
}
else
{
@if (p.IsRequired)
{ {
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))" /> <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 else
{ {
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" /> @if (p.IsRequired)
{
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))" />
}
else
{
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" />
}
} }
} </div>
</td> </div>
</tr> }
} }
} </div>
</table> </div>
<button type="button" class="btn btn-success" @onclick="Save">@SharedLocalizer["Save"]</button> <button type="button" class="btn btn-success" @onclick="Save">@SharedLocalizer["Save"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button> <button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
} }
@ -221,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;
@ -241,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;

View File

@ -8,75 +8,71 @@
@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 width="30%"> <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> <textarea class="form-control" @bind="@body" rows="5" readonly />
<td> </div>
<textarea class="form-control" @bind="@body" rows="5" readonly /> </div>
</td>
</tr>
} }
@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)

View File

@ -11,87 +11,78 @@
<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 width="30%"> <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 For="username" HelpText="A unique username for a user. Note that this field can not be modified once it is saved." ResourceKey="Username"></Label> <div class="col-sm-9">
</td>
<td>
<input id="username" class="form-control" @bind="@username" /> <input id="username" 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="password" HelpText="The user's password. Please choose a password which is sufficiently secure." ResourceKey="Password"></Label>
<Label For="password" HelpText="The user's password. Please choose a password which is sufficiently secure." ResourceKey="Password"></Label> <div class="col-sm-9">
</td>
<td>
<input id="password" type="password" class="form-control" @bind="@password" /> <input id="password" type="password" class="form-control" @bind="@password" />
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="confirm" HelpText="Please enter the password again to confirm it matches with the value above" ResourceKey="Confirm"></Label>
<Label For="confirm" HelpText="Please enter the password again to confirm it matches with the value above" ResourceKey="Confirm"></Label> <div class="col-sm-9">
</td>
<td>
<input id="confirm" type="password" class="form-control" @bind="@confirm" /> <input id="confirm" type="password" class="form-control" @bind="@confirm" />
</td> </div>
</tr> </div>
<tr> <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>
<Label For="email" HelpText="The email address where the user will receive notifications" ResourceKey="Email"></Label> <div class="col-sm-9">
</td>
<td>
<input id="email" class="form-control" @bind="@email" /> <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 For="displayname" HelpText="The full name of the user" ResourceKey="DisplayName"></Label> <div class="col-sm-9">
</td>
<td>
<input id="displayname" class="form-control" @bind="@displayname" /> <input id="displayname" class="form-control" @bind="@displayname" />
</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">
@foreach (Profile profile in profiles) <div class="row mb-1 align-items-center">
{ @foreach (Profile profile in profiles)
var p = profile;
if (p.Category != category)
{ {
<tr> var p = profile;
<th colspan="2" style="text-align: center;"> if (p.Category != category)
@p.Category {
</th> <div class="col text-center pb-2">
</tr> <strong>@p.Category</strong>
category = p.Category; </div>
category = p.Category;
}
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="@p.Name" HelpText="@p.Description">@p.Title</Label>
<div class="col-sm-9">
@if (p.IsRequired)
{
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))" />
}
else
{
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" />
}
</div>
</div>
} }
<tr>
<td width="30%"> </div>
<Label For="@p.Name" HelpText="@p.Description">@p.Title</Label> </div>
</td>
<td>
@if (p.IsRequired)
{
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))" />
}
else
{
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" />
}
</td>
</tr>
}
</table>
} }
</TabPanel> </TabPanel>
</TabStrip> </TabStrip>
<br />
<br />
<button type="button" class="btn btn-success" @onclick="SaveUser">@SharedLocalizer["Save"]</button> <button type="button" class="btn btn-success" @onclick="SaveUser">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink> <NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>

View File

@ -20,102 +20,106 @@ 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 width="30%"> <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 For="username" HelpText="The unique username for a user. Note that this field can not be modified." ResourceKey="Username"></Label> <div class="col-sm-9">
</td>
<td>
<input id="username" class="form-control" @bind="@username" readonly /> <input id="username" 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="password" HelpText="The user's password. Please choose a password which is sufficiently secure." ResourceKey="Password"></Label>
<Label For="password" HelpText="The user's password. Please choose a password which is sufficiently secure." ResourceKey="Password"></Label> <div class="col-sm-9">
</td>
<td>
<input id="password" type="password" class="form-control" @bind="@password" /> <input id="password" type="password" class="form-control" @bind="@password" />
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="confirm" HelpText="Please enter the password again to confirm it matches with the value above" ResourceKey="Confirm"></Label>
<Label For="confirm" HelpText="Please enter the password again to confirm it matches with the value above" ResourceKey="Confirm"></Label> <div class="col-sm-9">
</td>
<td>
<input id="confirm" type="password" class="form-control" @bind="@confirm" /> <input id="confirm" type="password" class="form-control" @bind="@confirm" />
</td> </div>
</tr> </div>
<tr> <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>
<Label For="email" HelpText="The email address where the user will receive notifications" ResourceKey="Email"></Label> <div class="col-sm-9">
</td>
<td>
<input id="email" class="form-control" @bind="@email" /> <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 For="displayname" HelpText="The full name of the user" ResourceKey="DisplayName"></Label> <div class="col-sm-9">
</td>
<td>
<input id="displayname" class="form-control" @bind="@displayname" /> <input id="displayname" class="form-control" @bind="@displayname" />
</td> </div>
</tr> </div>
<tr> <div class="row mb-1 align-items-center">
<td> <Label Class="col-sm-3" For="@photofileid.ToString()" HelpText="A photo of the user" ResourceKey="Photo"></Label>
<Label For="@photofileid.ToString()" HelpText="A photo of the user" ResourceKey="Photo"></Label> <div class="col-sm-9">
</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 For="isdeleted" HelpText="Indicate if the user is active" ResourceKey="IsDeleted"></Label> <div class="col-sm-9">
</td>
<td>
<select id="isdeleted" class="form-select" @bind="@isdeleted"> <select id="isdeleted" class="form-select" @bind="@isdeleted">
<option value="True">@SharedLocalizer["Yes"]</option> <option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option> <option value="False">@SharedLocalizer["No"]</option>
</select> </select>
</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">
@foreach (Profile profile in profiles) <div class="row mb-1 align-items-center">
{ @foreach (Profile profile in profiles)
var p = profile;
if (p.Category != category)
{ {
<tr> var p = profile;
<th colspan="2" style="text-align: center;"> if (p.Category != category)
@p.Category {
</th> <div class="col text-center pb-2">
</tr> <strong>@p.Category</strong>
category = p.Category; </div>
category = p.Category;
}
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="@p.Name" HelpText="@p.Description">@p.Title</Label>
<div class="col-sm-9">
@if (!string.IsNullOrEmpty(p.Options))
{
<select id="@p.Name" class="form-select" @onchange="@(e => ProfileChanged(e, p.Name))">
@foreach (var option in p.Options.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
@if (GetProfileValue(p.Name, "") == option || (GetProfileValue(p.Name, "") == "" && p.DefaultValue == option))
{
<option value="@option" selected>@option</option>
}
else
{
<option value="@option">@option</option>
}
}
</select>
}
else
{
@if (p.IsRequired)
{
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))" />
}
else
{
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" />
}
}
</div>
</div>
} }
<tr> </div>
<td width="30%"> </div>
<Label For="@p.Name" HelpText="@p.Description">@p.Title</Label>
</td>
<td>
@if (p.IsRequired)
{
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" required @onchange="@(e => ProfileChanged(e, p.Name))" />
}
else
{
<input id="@p.Name" class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" @onchange="@(e => ProfileChanged(e, p.Name))" />
}
</td>
</tr>
}
</table>
} }
</TabPanel> </TabPanel>
</TabStrip> </TabStrip>

View File

@ -14,33 +14,32 @@
} }
else else
{ {
<table class="table table-borderless"> <div class="container">
<tr> <div class="row mb-1 align-items-center">
<td> <div class="col-sm-4">
<div><ActionLink Action="Add" Text="Add User" ResourceKey="AddUser" /></div> <ActionLink Action="Add" Text="Add User" ResourceKey="AddUser" />
</td> </div>
<td> <div class="col-sm-4">
<input class="form-control" @bind="@_search" /> <input class="form-control" @bind="@_search" />
</td> </div>
<td> <div class="col-sm-4">
<button class="btn btn-secondary" @onclick="OnSearch">@SharedLocalizer["Search"]</button> <button class="btn btn-secondary" @onclick="OnSearch">@SharedLocalizer["Search"]</button>
</td> </div>
</tr> </div>
</table> </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>@SharedLocalizer["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="@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.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" />
@ -96,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

@ -12,20 +12,16 @@
} }
else else
{ {
<table class="table table-borderless"> <div class="container">
<tr> <div class="row mb-1 align-items-center">
<td width="30%"> <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>
<td>
<select id="role" class="form-select" @bind="@roleid"> <select id="role" class="form-select" @bind="@roleid">
<option value="-1">&lt;@Localizer["Role.Select"]&gt;</option> <option value="-1">&lt;@Localizer["Role.Select"]&gt;</option>
@foreach (Role role in roles) @foreach (Role role in roles)
@ -33,36 +29,34 @@ else
<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>
</div>
<br />
<br />
<button type="button" class="btn btn-success" @onclick="SaveUserRole">@SharedLocalizer["Save"]</button> <button type="button" class="btn btn-success" @onclick="SaveUserRole">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink> <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">
<Header> <Header>
<th>@Localizer["Roles"]</th> <th>@Localizer["Roles"]</th>
<th>@Localizer["Effective"]</th> <th>@Localizer["Effective"]</th>
<th>@Localizer["Expiry"]</th> <th>@Localizer["Expiry"]</th>
<th>&nbsp;</th> <th>&nbsp;</th>
</Header> </Header>
<Row> <Row>
<td>@context.Role.Name</td> <td>@context.Role.Name</td>

View File

@ -58,7 +58,6 @@
</span> </span>
</div> </div>
} }
<ModuleMessage Message="@_message" Type="@_messagetype"></ModuleMessage>
</div> </div>
@if (_image != string.Empty) @if (_image != string.Empty)
{ {
@ -67,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>
} }
@ -89,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
@ -104,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"
@ -112,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))
@ -119,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 } };
@ -135,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))
@ -218,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();
@ -230,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;
@ -272,16 +302,14 @@
_message = Localizer["Success.File.Upload"]; _message = Localizer["Success.File.Upload"];
_messagetype = MessageType.Success; _messagetype = MessageType.Success;
// set FileId to first file in upload collection
await GetFiles(); await GetFiles();
var file = _files.Where(item => item.Name == upload[0]).FirstOrDefault();
if (upload.Length == 1) if (file != null)
{ {
var file = _files.Where(item => item.Name == upload[0]).FirstOrDefault(); FileId = file.FileId;
if (file != null) await SetImage();
{ await OnUpload.InvokeAsync(FileId);
FileId = file.FileId;
await SetImage();
}
} }
StateHasChanged(); StateHasChanged();
} }
@ -315,6 +343,7 @@
{ {
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["Success.File.Delete"]; _message = Localizer["Success.File.Delete"];
_messagetype = MessageType.Success; _messagetype = MessageType.Success;

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,39 +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();
if (string.IsNullOrEmpty(Class)) if (!string.IsNullOrEmpty(HelpText))
{ {
Class = "form-label"; _helptext = Localize(nameof(HelpText), HelpText);
_spanclass += (!string.IsNullOrEmpty(Class)) ? " " + Class : "";
} }
else
_openLabel = "<label";
if (!string.IsNullOrEmpty(For))
{ {
_openLabel += " for=\"" + For + "\""; _labelclass += (!string.IsNullOrEmpty(Class)) ? " " + Class : "";
} }
if (!string.IsNullOrEmpty(Class))
{
_openLabel += " 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,7 +4,7 @@
@if (!string.IsNullOrEmpty(_message)) @if (!string.IsNullOrEmpty(_message))
{ {
<div class="@_classname alert-dismissible fade show" 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))
{ {
@ -12,7 +12,6 @@
} }
<button type="button" class="btn-close" aria-label="Close" @onclick="DismissModal"></button> <button type="button" class="btn-close" aria-label="Close" @onclick="DismissModal"></button>
</div> </div>
<br />
} }
@code { @code {

View File

@ -2,45 +2,57 @@
@inherits ModuleControlBase @inherits ModuleControlBase
@typeparam TableItem @typeparam TableItem
<p> @if (ItemList != null)
@if (Toolbar == "Top") {
@if (Toolbar == "Top" && _pages > 0 && Items.Count() > _maxItems)
{ {
<div class="mx-auto text-center"> <ul class="pagination justify-content-center my-2">
@if (_endPage > 1) <li class="page-item@((_page > 1) ? "" : " disabled")">
<a class="page-link" @onclick=@(async () => UpdateList(1))><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
</li>
@if (_pages > _displayPages)
{ {
<button class="btn btn-secondary m-1" @onclick=@(async () => UpdateList(1))><span class="oi oi-media-step-backward" title="first" aria-hidden="true"></span></button> <li class="page-item@((_page > _displayPages) ? "" : " disabled")">
<a class="page-link" @onclick=@(async () => SkipPages("back"))><span class="oi oi-media-skip-backward" title="skip back" aria-hidden="true"></span></a>
</li>
} }
@if (_page > _maxPages) <li class="page-item@((_page > 1) ? "" : " disabled")">
<a class="page-link" @onclick=@(async () => NavigateToPage("previous"))><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></a>
</li>
@for (int i = _startPage; i <= _endPage; i++)
{ {
<button class="btn btn-secondary m-1" @onclick=@(async () => SetPagerSize("back"))><span class="oi oi-media-skip-backward" title="back" aria-hidden="true"></span></button> var pager = i;
} if (pager == _page)
@if (_endPage > 1)
{
<button class="btn btn-secondary m-1" @onclick=@(async () => NavigateToPage("previous"))><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></button>
@for (int i = _startPage; i <= _endPage; i++)
{ {
var pager = i; <li class="page-item active">
<button class="btn @((pager == _page) ? "btn-primary" : "btn-link")" @onclick=@(async () => UpdateList(pager))> <a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a>
@pager </li>
</button> }
else
{
<li class="page-item">
<a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a>
</li>
} }
<button class="btn btn-secondary m-1" @onclick=@(async () => NavigateToPage("next"))><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></button>
} }
@if (_endPage < _pages) <li class="page-item@((_page < _pages) ? "" : " disabled")">
<a class="page-link" @onclick=@(async () => NavigateToPage("next"))><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></a>
</li>
@if (_pages > _displayPages)
{ {
<button class="btn btn-secondary m-1" @onclick=@(async () => SetPagerSize("forward"))><span class="oi oi-media-skip-forward" title="forward" aria-hidden="true"></span></button> <li class="page-item@((_endPage < _pages) ? "" : " disabled")">
<a class="page-link" @onclick=@(async () => SkipPages("forward"))><span class="oi oi-media-skip-forward" title="skip forward" aria-hidden="true"></span></a>
</li>
} }
@if (_endPage > 1) <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>
<button class="btn btn-secondary m-1" @onclick=@(async () => UpdateList(_pages))><span class="oi oi-media-step-forward" title="last" aria-hidden="true"></span></button> </li>
} <li class="page-item disabled">
@if (_endPage > 1) <a class="page-link">Page @_page of @_pages</a>
{ </li>
<span class="btn btn-link disabled">Page @_page of @_pages</span> </ul>
}
</div>
} }
@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> </div>
} }
@if (Toolbar == "Bottom") @if (Toolbar == "Bottom" && _pages > 0 && Items.Count() > _maxItems)
{ {
<div class="mx-auto text-center"> <ul class="pagination justify-content-center my-2">
@if (_endPage > 1) <li class="page-item@((_page > 1) ? "" : " disabled")">
<a class="page-link" @onclick=@(async () => UpdateList(1))><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
</li>
@if (_pages > _displayPages)
{ {
<button class="btn btn-secondary mr-1" @onclick=@(async () => UpdateList(1))><span class="oi oi-media-step-backward" title="first" aria-hidden="true"></span></button> <li class="page-item@((_page > _displayPages) ? "" : " disabled")">
<a class="page-link" @onclick=@(async () => SkipPages("back"))><span class="oi oi-media-skip-backward" title="skip back" aria-hidden="true"></span></a>
</li>
} }
@if (_page > _maxPages) <li class="page-item@((_page > 1) ? "" : " disabled")">
<a class="page-link" @onclick=@(async () => NavigateToPage("previous"))><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></a>
</li>
@for (int i = _startPage; i <= _endPage; i++)
{ {
<button class="btn btn-secondary mr-1" @onclick=@(async () => SetPagerSize("back"))><span class="oi oi-media-skip-backward" title="back" aria-hidden="true"></span></button> var pager = i;
} if (pager == _page)
@if (_endPage > 1)
{
<button class="btn btn-secondary mr-1" @onclick=@(async () => NavigateToPage("previous"))><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></button>
@for (int i = _startPage; i <= _endPage; i++)
{ {
var pager = i; <li class="page-item active">
<button class="btn @((pager == _page) ? "btn-primary" : "btn-link")" @onclick=@(async () => UpdateList(pager))> <a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a>
@pager </li>
</button> }
else
{
<li class="page-item">
<a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a>
</li>
} }
<button class="btn btn-secondary mr-1" @onclick=@(async () => NavigateToPage("next"))><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></button>
} }
@if (_endPage < _pages) <li class="page-item@((_page < _pages) ? "" : " disabled")">
<a class="page-link" @onclick=@(async () => NavigateToPage("next"))><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></a>
</li>
@if (_pages > _displayPages)
{ {
<button class="btn btn-secondary mr-1" @onclick=@(async () => SetPagerSize("forward"))><span class="oi oi-media-skip-forward" title="forward" aria-hidden="true"></span></button> <li class="page-item@((_endPage < _pages) ? "" : " disabled")">
<a class="page-link" @onclick=@(async () => SkipPages("forward"))><span class="oi oi-media-skip-forward" title="skip forward" aria-hidden="true"></span></a>
</li>
} }
@if (_endPage > 1) <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>
<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>
} <li class="page-item disabled">
@if (_endPage > 1) <a class="page-link">Page @_page of @_pages</a>
{ </li>
<span class="btn btn-link disabled">Page @_page of @_pages</span> </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 UpdateList(int currentPage) public void SetPagerSize()
{ {
ItemList = Items.Skip((currentPage - 1) * _maxItems).Take(_maxItems); _startPage = ((_page - 1) / _displayPages) * _displayPages + 1;
_page = currentPage; _endPage = _startPage + _displayPages - 1;
if (_endPage > _pages)
{
_endPage = _pages;
}
StateHasChanged(); StateHasChanged();
} }
public void SetPagerSize(string direction) public void UpdateList(int page)
{ {
if (direction == "forward") ItemList = Items.Skip((page - 1) * _maxItems).Take(_maxItems);
{ _page = page;
if (_endPage + 1 < _pages) SetPagerSize();
{ }
_startPage = _endPage + 1;
}
else
{
_startPage = 1;
}
if (_endPage + _maxPages < _pages) public void SkipPages(string direction)
{ {
_endPage = _startPage + _maxPages - 1; switch (direction)
}
else
{
_endPage = _pages;
}
StateHasChanged();
}
else if (direction == "back")
{ {
_endPage = _startPage - 1; case "forward":
_startPage = _startPage - _maxPages; _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)
{ {
if (_page < _pages) case "next":
{ if (_page < _pages)
if (_page == _endPage)
{ {
SetPagerSize("forward"); _page += 1;
} }
_page += 1; break;
} case "previous":
} if (_page > 1)
else if (direction == "previous")
{
if (_page > 1)
{
if (_page == _startPage)
{ {
SetPagerSize("back"); _page -= 1;
} }
_page -= 1; break;
}
} }
UpdateList(_page); UpdateList(_page);

View File

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

View File

@ -2,7 +2,7 @@
@namespace Oqtane.Modules.HtmlText @namespace Oqtane.Modules.HtmlText
@inherits ModuleBase @inherits ModuleBase
@inject IHtmlTextService HtmlTextService @inject IHtmlTextService HtmlTextService
@inject IStringLocalizer<Edit> Localizer @inject IStringLocalizer<Index> Localizer
@((MarkupString)content) @((MarkupString)content)
@ -16,7 +16,7 @@
@code { @code {
public override List<Resource> Resources => new List<Resource>() public override List<Resource> Resources => new List<Resource>()
{ {
new Resource { ResourceType = ResourceType.Stylesheet, Url = ModulePath() + "Module.css" } new Resource { ResourceType = ResourceType.Stylesheet, Url = ModulePath() + "Module.css" }
}; };

View File

@ -5,46 +5,44 @@
@inject IStringLocalizer<Settings> Localizer @inject IStringLocalizer<Settings> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer @inject IStringLocalizer<SharedResources> SharedLocalizer
<table class="table table-borderless"> <div class="container">
<tr> <div class="row mb-1 align-items-center">
<td width="30%"> <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-select" @bind="@_allowfilemanagement"> <option value="false">@SharedLocalizer["No"]</option>
<option value="true">@SharedLocalizer["Yes"]</option> </select>
<option value="false">@SharedLocalizer["No"]</option> </div>
</select> </div>
</td> </div>
</tr>
</table>
@code { @code {
private string _allowfilemanagement; private string _allowfilemanagement;
protected override void OnInitialized() protected override void OnInitialized()
{
try
{ {
_allowfilemanagement = SettingService.GetSetting(ModuleState.Settings, "AllowFileManagement", "true"); try
{
_allowfilemanagement = SettingService.GetSetting(ModuleState.Settings, "AllowFileManagement", "true");
}
catch (Exception ex)
{
ModuleInstance.AddModuleMessage(ex.Message, MessageType.Error);
}
} }
catch (Exception ex)
public async Task UpdateSettings()
{ {
ModuleInstance.AddModuleMessage(ex.Message, MessageType.Error); try
{
var settings = ModuleState.Settings;
settings = SettingService.SetSetting(settings, "AllowFileManagement", _allowfilemanagement);
await SettingService.UpdateModuleSettingsAsync(settings, ModuleState.ModuleId);
}
catch (Exception ex)
{
ModuleInstance.AddModuleMessage(ex.Message, MessageType.Error);
}
} }
} }
public async Task UpdateSettings()
{
try
{
var settings = ModuleState.Settings;
settings = SettingService.SetSetting(settings, "AllowFileManagement", _allowfilemanagement);
await SettingService.UpdateModuleSettingsAsync(settings, ModuleState.ModuleId);
}
catch (Exception ex)
{
ModuleInstance.AddModuleMessage(ex.Message, MessageType.Error);
}
}
}

View File

@ -134,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>();
@ -205,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;
@ -215,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
@ -259,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);
@ -269,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);
@ -279,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);
@ -289,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);
@ -299,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);
@ -309,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.2.0</Version> <Version>2.3.1</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.2.0</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v2.3.1</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>
@ -38,4 +38,15 @@
<Folder Include="Resources\" /> <Folder Include="Resources\" />
<Folder Include="Resources\Themes\Controls\Theme\" /> <Folder Include="Resources\Themes\Controls\Theme\" />
</ItemGroup> </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

@ -153,4 +153,10 @@
<data name="Pwd.HelpText" xml:space="preserve"> <data name="Pwd.HelpText" xml:space="preserve">
<value>Enter the password to use for the database</value> <value>Enter the password to use for the database</value>
</data> </data>
<data name="Custom" xml:space="preserve">
<value>Custom</value>
</data>
<data name="Integrated" xml:space="preserve">
<value>Integrated</value>
</data>
</root> </root>

View File

@ -147,4 +147,10 @@
<data name="Pwd.HelpText" xml:space="preserve"> <data name="Pwd.HelpText" xml:space="preserve">
<value>Enter the password to use for the database</value> <value>Enter the password to use for the database</value>
</data> </data>
<data name="Custom" xml:space="preserve">
<value>Custom</value>
</data>
<data name="Integrated" xml:space="preserve">
<value>Integrated</value>
</data>
</root> </root>

View File

@ -120,8 +120,8 @@
<data name="DatabaseConfig" xml:space="preserve"> <data name="DatabaseConfig" xml:space="preserve">
<value>Database Configuration</value> <value>Database Configuration</value>
</data> </data>
<data name="DatabaseType" xml:space="preserve"> <data name="DatabaseType.Text" xml:space="preserve">
<value>Database Type:</value> <value>Database:</value>
</data> </data>
<data name="ApplicationAdmin" xml:space="preserve"> <data name="ApplicationAdmin" xml:space="preserve">
<value>Application Administrator</value> <value>Application Administrator</value>
@ -138,4 +138,31 @@
<data name="Register" xml:space="preserve"> <data name="Register" xml:space="preserve">
<value>Please Register Me For Major Product Updates And Security Bulletins</value> <value>Please Register Me For Major Product Updates And Security Bulletins</value>
</data> </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> </root>

View File

@ -156,4 +156,10 @@
<data name="UploadFiles.Heading" xml:space="preserve"> <data name="UploadFiles.Heading" xml:space="preserve">
<value>Upload Files</value> <value>Upload Files</value>
</data> </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> </root>

View File

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

View File

@ -165,4 +165,22 @@
<data name="DeleteFolder.Message" xml:space="preserve"> <data name="DeleteFolder.Message" xml:space="preserve">
<value>Are You Sure You Wish To Delete This Folder?</value> <value>Are You Sure You Wish To Delete This Folder?</value>
</data> </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> </root>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<root> <root>
<!-- <!--
Microsoft ResX Schema Microsoft ResX Schema
@ -154,16 +154,16 @@
<value>Select how often you want the job to run</value> <value>Select how often you want the job to run</value>
</data> </data>
<data name="Starting.HelpText" xml:space="preserve"> <data name="Starting.HelpText" xml:space="preserve">
<value>What time do you want the job to start</value> <value>Optionally enter the date and time when this job should start executing</value>
</data> </data>
<data name="Ending.HelpText" xml:space="preserve"> <data name="Ending.HelpText" xml:space="preserve">
<value>When do you want the job to end</value> <value>Optionally enter the date and time when this job should stop executing</value>
</data> </data>
<data name="RetentionLog.HelpText" xml:space="preserve"> <data name="RetentionLog.HelpText" xml:space="preserve">
<value>Number of log entries to retain for this job</value> <value>Number of log entries to retain for this job</value>
</data> </data>
<data name="NextExecution.HelpText" xml:space="preserve"> <data name="NextExecution.HelpText" xml:space="preserve">
<value>Next execution for this job.</value> <value>Optionally modify the date and time when this job should execute next</value>
</data> </data>
<data name="Type.Text" xml:space="preserve"> <data name="Type.Text" xml:space="preserve">
<value>Type: </value> <value>Type: </value>

View File

@ -121,9 +121,15 @@
<value>Export</value> <value>Export</value>
</data> </data>
<data name="Content.HelpText" xml:space="preserve"> <data name="Content.HelpText" xml:space="preserve">
<value>Enter the module content</value> <value>The Exported Module Content</value>
</data> </data>
<data name="Content.Text" xml:space="preserve"> <data name="Content.Text" xml:space="preserve">
<value>Content: </value> <value>Content: </value>
</data> </data>
<data name="Error.Module.Export" xml:space="preserve">
<value>Error Exporting Module Content</value>
</data>
<data name="Success.Content.Export" xml:space="preserve">
<value>Content Exported Successfully</value>
</data>
</root> </root>

View File

@ -118,7 +118,7 @@
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader> </resheader>
<data name="Content.HelpText" xml:space="preserve"> <data name="Content.HelpText" xml:space="preserve">
<value>Enter the module content</value> <value>Enter The Module Content To Import</value>
</data> </data>
<data name="Content.Text" xml:space="preserve"> <data name="Content.Text" xml:space="preserve">
<value>Content: </value> <value>Content: </value>
@ -133,7 +133,7 @@
<value>A Problem Was Encountered Importing Content. Please Ensure The Content Is Formatted Correctly For The Module.</value> <value>A Problem Was Encountered Importing Content. Please Ensure The Content Is Formatted Correctly For The Module.</value>
</data> </data>
<data name="Error.Module.Import" xml:space="preserve"> <data name="Error.Module.Import" xml:space="preserve">
<value>Error Importing Module</value> <value>Error Importing Module Content</value>
</data> </data>
<data name="Message.Required.ImportContent" xml:space="preserve"> <data name="Message.Required.ImportContent" xml:space="preserve">
<value>You Must Enter Some Content To Import</value> <value>You Must Enter Some Content To Import</value>

View File

@ -228,4 +228,7 @@
<data name="Appearance.Name" xml:space="preserve"> <data name="Appearance.Name" xml:space="preserve">
<value>Appearance</value> <value>Appearance</value>
</data> </data>
<data name="Message.Page.Deleted" xml:space="preserve">
<value>A page with path {0} already exists for the selected parent page in the Recycle Bin. Either recover the page or remove from the Recycle Bin and create it again.</value>
</data>
</root> </root>

View File

@ -148,7 +148,7 @@
<value>Error Adding User</value> <value>Error Adding User</value>
</data> </data>
<data name="Confirm.HelpText" xml:space="preserve"> <data name="Confirm.HelpText" xml:space="preserve">
<value>If you are changing your password you must enter it again to confirm it matches</value> <value>Enter your password again to confirm it matches the value entered above</value>
</data> </data>
<data name="Confirm.Text" xml:space="preserve"> <data name="Confirm.Text" xml:space="preserve">
<value>Confirm Password:</value> <value>Confirm Password:</value>
@ -166,7 +166,7 @@
<value>Email:</value> <value>Email:</value>
</data> </data>
<data name="Password.HelpText" xml:space="preserve"> <data name="Password.HelpText" xml:space="preserve">
<value>If you wish to change your password you can enter it here. Please choose a sufficiently secure password.</value> <value>Please choose a sufficiently secure password and enter it here</value>
</data> </data>
<data name="Password.Text" xml:space="preserve"> <data name="Password.Text" xml:space="preserve">
<value>Password:</value> <value>Password:</value>

View File

@ -217,10 +217,10 @@
<value>Enter the password for the integrated security</value> <value>Enter the password for the integrated security</value>
</data> </data>
<data name="HostUsername.HelpText" xml:space="preserve"> <data name="HostUsername.HelpText" xml:space="preserve">
<value>Enter the username of the host for this site</value> <value>Enter a valid host username</value>
</data> </data>
<data name="HostPassword.HelpText" xml:space="preserve"> <data name="HostPassword.HelpText" xml:space="preserve">
<value>Enter the password for the host of this site</value> <value>Enter a valid host password</value>
</data> </data>
<data name="Name.Text" xml:space="preserve"> <data name="Name.Text" xml:space="preserve">
<value>Site Name: </value> <value>Site Name: </value>

View File

@ -273,10 +273,43 @@
<data name="FullName" xml:space="preserve"> <data name="FullName" xml:space="preserve">
<value>Full Name:</value> <value>Full Name:</value>
</data> </data>
<data name="LocalVersion" xml:space="preserve"> <data name="LocalVersion" xml:space="preserve">
<value>Local Version</value> <value>Local Version</value>
</data> </data>
<data name="Search.Source" xml:space="preserve"> <data name="Search.Source" xml:space="preserve">
<value>source</value> <value>source</value>
</data> </data>
<data name="Message.InfoRequired" xml:space="preserve">
<value>Please Provide All Required Information</value>
</data>
<data name="Free" xml:space="preserve">
<value>Free</value>
</data>
<data name="Paid" xml:space="preserve">
<value>Paid</value>
</data>
<data name="Search.Price" xml:space="preserve">
<value>price</value>
</data>
<data name="Accept" xml:space="preserve">
<value>Accept</value>
</data>
<data name="License Not Specified" xml:space="preserve">
<value>License Not Specified</value>
</data>
<data name="Review License Terms" xml:space="preserve">
<value>Review License Terms</value>
</data>
<data name="Trial" xml:space="preserve">
<value>Day Trial</value>
</data>
<data name="Expires" xml:space="preserve">
<value>Expires</value>
</data>
<data name="Extend" xml:space="preserve">
<value>Extend</value>
</data>
<data name="Not Specified" xml:space="preserve">
<value>Not Specified</value>
</data>
</root> </root>

View File

@ -1,65 +1,65 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<root> <root>
<!-- <!--
Microsoft ResX Schema Microsoft ResX Schema
Version 2.0 Version 2.0
The primary goals of this format is to allow a simple XML format The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes various data types are done through the TypeConverter classes
associated with the data types. associated with the data types.
Example: Example:
... ado.net/XML headers & schema ... ... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader> <resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader> <resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader> <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, 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="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="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64"> <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value> <value>[base64 mime encoded serialized .NET Framework object]</value>
</data> </data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64"> <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> <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment> <comment>This is a comment</comment>
</data> </data>
There are any number of "resheader" rows that contain simple There are any number of "resheader" rows that contain simple
name/value pairs. name/value pairs.
Each data row contains a name, and value. The row also contains a Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture. text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the Classes that don't support this are serialized and stored with the
mimetype set. mimetype set.
The mimetype is used for serialized objects, and tells the The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly: extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below. read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64 mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding. : and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64 mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding. : and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64 mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter : using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding. : 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: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:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true"> <xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType> <xsd:complexType>
@ -117,10 +117,28 @@
<resheader name="writer"> <resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader> </resheader>
<data name="Title.HelpText" xml:space="preserve"> <data name="Footer.HelpText" xml:space="preserve">
<value>Specify If The Page Footer Should Be Displayed</value> <value>Specify if a Footer pane should always be displayed in a fixed location at the bottom of the page.</value>
</data> </data>
<data name="Title.Text" xml:space="preserve"> <data name="Footer.Text" xml:space="preserve">
<value>Display Footer?</value> <value>Display Footer?</value>
</data> </data>
<data name="Login.HelpText" xml:space="preserve">
<value>Specify if a Login option should be displayed, Note that this option does not prevent the login page from being accessible via a direct url.</value>
</data>
<data name="Login.Text" xml:space="preserve">
<value>Show Login?</value>
</data>
<data name="Page" xml:space="preserve">
<value>Page</value>
</data>
<data name="Register.HelpText" xml:space="preserve">
<value>Specify if a Register option should be displayed. Note that this option is also dependent on the Allow Registration option in Site Settings.</value>
</data>
<data name="Register.Text" xml:space="preserve">
<value>Show Register?</value>
</data>
<data name="Site" xml:space="preserve">
<value>Site</value>
</data>
</root> </root>

View File

@ -67,9 +67,9 @@ namespace Oqtane.Services
await DeleteAsync($"{Apiurl}/{fileId}"); await DeleteAsync($"{Apiurl}/{fileId}");
} }
public async Task<File> UploadFileAsync(string url, int folderId) public async Task<File> UploadFileAsync(string url, int folderId, string name)
{ {
return await GetJsonAsync<File>($"{Apiurl}/upload?url={WebUtility.UrlEncode(url)}&folderid={folderId}"); return await GetJsonAsync<File>($"{Apiurl}/upload?url={WebUtility.UrlEncode(url)}&folderid={folderId}&name={name}");
} }
public async Task<string> UploadFilesAsync(int folderId, string[] files, string id) public async Task<string> UploadFilesAsync(int folderId, string[] files, string id)

View File

@ -25,8 +25,6 @@ namespace Oqtane.Services
public async Task<Installation> IsInstalled() public async Task<Installation> IsInstalled()
{ {
// add antiforgerytoken header so that it is included on all HttpClient calls for the lifetime of the app
AddRequestHeader(Constants.AntiForgeryTokenHeaderName, _siteState.AntiForgeryToken);
var path = new Uri(_navigationManager.Uri).LocalPath.Substring(1); var path = new Uri(_navigationManager.Uri).LocalPath.Substring(1);
return await GetJsonAsync<Installation>($"{ApiUrl}/installed/?path={WebUtility.UrlEncode(path)}"); return await GetJsonAsync<Installation>($"{ApiUrl}/installed/?path={WebUtility.UrlEncode(path)}");
} }
@ -50,5 +48,14 @@ namespace Oqtane.Services
{ {
await PostJsonAsync($"{ApiUrl}/register?email={WebUtility.UrlEncode(email)}", true); await PostJsonAsync($"{ApiUrl}/register?email={WebUtility.UrlEncode(email)}", true);
} }
public void SetAntiForgeryTokenHeader(string antiforgerytokenvalue)
{
if (!string.IsNullOrEmpty(antiforgerytokenvalue))
{
AddRequestHeader(Constants.AntiForgeryTokenHeaderName, antiforgerytokenvalue);
}
}
} }
} }

View File

@ -4,8 +4,15 @@ using System.Threading.Tasks;
namespace Oqtane.Services namespace Oqtane.Services
{ {
/// <summary>
/// Service to retrieve <see cref="Database"/> information.
/// </summary>
public interface IDatabaseService public interface IDatabaseService
{ {
/// <summary>
/// Returns a list of databases
/// </summary>
/// <returns></returns>
Task<List<Database>> GetDatabasesAsync(); Task<List<Database>> GetDatabasesAsync();
} }
} }

View File

@ -62,8 +62,9 @@ namespace Oqtane.Services
/// </summary> /// </summary>
/// <param name="url"></param> /// <param name="url"></param>
/// <param name="folderId"></param> /// <param name="folderId"></param>
/// <param name="name"></param>
/// <returns></returns> /// <returns></returns>
Task<File> UploadFileAsync(string url, int folderId); Task<File> UploadFileAsync(string url, int folderId, string name);
/// <summary> /// <summary>
/// Upload one or more files. /// Upload one or more files.

View File

@ -4,12 +4,48 @@ using Oqtane.Shared;
namespace Oqtane.Services namespace Oqtane.Services
{ {
/// <summary>
/// Service to manage (install master database / upgrade version / etc.) the installation
/// </summary>
public interface IInstallationService public interface IInstallationService
{ {
/// <summary>
/// Returns a status/message object with the current installation state
/// </summary>
/// <returns></returns>
Task<Installation> IsInstalled(); Task<Installation> IsInstalled();
/// <summary>
/// Starts the installation process
/// </summary>
/// <param name="config">connectionString, database type, alias etc.</param>
/// <returns>internal status/message object</returns>
Task<Installation> Install(InstallConfig config); Task<Installation> Install(InstallConfig config);
/// <summary>
/// Starts the upgrade process
/// </summary>
/// <returns>internal status/message object</returns>
Task<Installation> Upgrade(); Task<Installation> Upgrade();
/// <summary>
/// Restarts the installation
/// </summary>
/// <returns>internal status/message object</returns>
Task RestartAsync(); Task RestartAsync();
/// <summary>
/// Registers a new <see cref="User"/>
/// </summary>
/// <param name="email">Email of the user to be registered</param>
/// <returns></returns>
Task RegisterAsync(string email); Task RegisterAsync(string email);
/// <summary>
/// Sets the antiforgerytoken header so that it is included on all HttpClient calls for the lifetime of the app
/// </summary>
/// <returns></returns>
void SetAntiForgeryTokenHeader(string antiforgerytokenvalue);
} }
} }

View File

@ -4,10 +4,22 @@ using System.Threading.Tasks;
namespace Oqtane.Services namespace Oqtane.Services
{ {
/// <summary>
/// Service to read the job schedule log
/// </summary>
public interface IJobLogService public interface IJobLogService
{ {
/// <summary>
/// Return a list of all <see cref="JobLog"/> entries
/// </summary>
/// <returns></returns>
Task<List<JobLog>> GetJobLogsAsync(); Task<List<JobLog>> GetJobLogsAsync();
/// <summary>
/// Return a <see cref="JobLog"/> entry for the given Id
/// </summary>
/// <param name="jobLogId"></param>
/// <returns></returns>
Task<JobLog> GetJobLogAsync(int jobLogId); Task<JobLog> GetJobLogAsync(int jobLogId);
} }
} }

View File

@ -1,23 +1,61 @@
using Oqtane.Models; using Oqtane.Models;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Oqtane.Services namespace Oqtane.Services
{ {
/// <summary>
/// Service to manage jobs (<see cref="Job"/>)
/// </summary>
public interface IJobService public interface IJobService
{ {
/// <summary>
/// Returns a list of all jobs
/// </summary>
/// <returns></returns>
Task<List<Job>> GetJobsAsync(); Task<List<Job>> GetJobsAsync();
/// <summary>
/// Return a specific job
/// </summary>
/// <param name="jobId"></param>
/// <returns></returns>
Task<Job> GetJobAsync(int jobId); Task<Job> GetJobAsync(int jobId);
/// <summary>
/// Adds a new job
/// </summary>
/// <param name="job"></param>
/// <returns></returns>
Task<Job> AddJobAsync(Job job); Task<Job> AddJobAsync(Job job);
/// <summary>
/// Updates an existing job
/// </summary>
/// <param name="job"></param>
/// <returns></returns>
Task<Job> UpdateJobAsync(Job job); Task<Job> UpdateJobAsync(Job job);
/// <summary>
/// Delete an existing job
/// </summary>
/// <param name="jobId"></param>
/// <returns></returns>
Task DeleteJobAsync(int jobId); Task DeleteJobAsync(int jobId);
/// <summary>
/// Starts the given job
/// </summary>
/// <param name="jobId"></param>
/// <returns></returns>
Task StartJobAsync(int jobId); Task StartJobAsync(int jobId);
/// <summary>
/// Stops the given job
/// </summary>
/// <param name="jobId"></param>
/// <returns></returns>
Task StopJobAsync(int jobId); Task StopJobAsync(int jobId);
} }
} }

View File

@ -4,14 +4,38 @@ using System.Threading.Tasks;
namespace Oqtane.Services namespace Oqtane.Services
{ {
/// <summary>
/// Service to manage <see cref="Language"/> entries
/// </summary>
public interface ILanguageService public interface ILanguageService
{ {
/// <summary>
/// Returns a list of all available languages for the given <see cref="Site"/>
/// </summary>
/// <param name="siteId"></param>
/// <returns></returns>
Task<List<Language>> GetLanguagesAsync(int siteId); Task<List<Language>> GetLanguagesAsync(int siteId);
/// <summary>
/// Returns the given language
/// </summary>
/// <param name="languageId"></param>
/// <returns></returns>
Task<Language> GetLanguageAsync(int languageId); Task<Language> GetLanguageAsync(int languageId);
/// <summary>
/// Adds the given language
/// </summary>
/// <param name="language"></param>
/// <returns></returns>
Task<Language> AddLanguageAsync(Language language); Task<Language> AddLanguageAsync(Language language);
/// <summary>
/// Deletes the given language
/// </summary>
/// <param name="languageId"></param>
/// <returns></returns>
Task DeleteLanguageAsync(int languageId); Task DeleteLanguageAsync(int languageId);
} }
} }

View File

@ -4,8 +4,15 @@ using Oqtane.Models;
namespace Oqtane.Services namespace Oqtane.Services
{ {
/// <summary>
/// Service to retrieve localizations (<see cref="Culture"/>)
/// </summary>
public interface ILocalizationService public interface ILocalizationService
{ {
/// <summary>
/// Returns a collection of supported cultures
/// </summary>
/// <returns></returns>
Task<IEnumerable<Culture>> GetCulturesAsync(); Task<IEnumerable<Culture>> GetCulturesAsync();
} }
} }

View File

@ -1,4 +1,4 @@
using Oqtane.Models; using Oqtane.Models;
using Oqtane.Shared; using Oqtane.Shared;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -7,11 +7,59 @@ using Oqtane.Enums;
namespace Oqtane.Services namespace Oqtane.Services
{ {
/// <summary>
/// Service to retrieve and store <see cref="Log"/> entries
/// </summary>
public interface ILogService public interface ILogService
{ {
/// <summary>
/// Returns a list of log entires for the given params
/// </summary>
/// <param name="siteId"></param>
/// <param name="level"></param>
/// <param name="function"></param>
/// <param name="rows"></param>
/// <returns></returns>
Task<List<Log>> GetLogsAsync(int siteId, string level, string function, int rows); Task<List<Log>> GetLogsAsync(int siteId, string level, string function, int rows);
/// <summary>
/// Returns a specific log entry for the given id
/// </summary>
/// <param name="logId"></param>
/// <returns></returns>
Task<Log> GetLogAsync(int logId); Task<Log> GetLogAsync(int logId);
/// <summary>
/// Creates a new log entry
/// </summary>
/// <param name="pageId"></param>
/// <param name="moduleId"></param>
/// <param name="userId"></param>
/// <param name="category"></param>
/// <param name="feature"></param>
/// <param name="function"></param>
/// <param name="level"></param>
/// <param name="exception"></param>
/// <param name="message"></param>
/// <param name="args"></param>
/// <returns></returns>
Task Log(int? pageId, int? moduleId, int? userId, string category, string feature, LogFunction function, LogLevel level, Exception exception, string message, params object[] args); Task Log(int? pageId, int? moduleId, int? userId, string category, string feature, LogFunction function, LogLevel level, Exception exception, string message, params object[] args);
/// <summary>
/// Creates a new log entry
/// </summary>
/// <param name="alias"></param>
/// <param name="pageId"></param>
/// <param name="moduleId"></param>
/// <param name="userId"></param>
/// <param name="category"></param>
/// <param name="feature"></param>
/// <param name="function"></param>
/// <param name="level"></param>
/// <param name="exception"></param>
/// <param name="message"></param>
/// <param name="args"></param>
/// <returns></returns>
Task Log(Alias alias, int? pageId, int? moduleId, int? userId, string category, string feature, LogFunction function, LogLevel level, Exception exception, string message, params object[] args); Task Log(Alias alias, int? pageId, int? moduleId, int? userId, string category, string feature, LogFunction function, LogLevel level, Exception exception, string message, params object[] args);
} }
} }

View File

@ -5,14 +5,60 @@ using System.Threading.Tasks;
namespace Oqtane.Services namespace Oqtane.Services
{ {
/// <summary>
/// Service to manage a <see cref="ModuleDefinition"/>
/// </summary>
public interface IModuleDefinitionService public interface IModuleDefinitionService
{ {
/// <summary>
/// Returns a list of module definitions for the given site
/// </summary>
/// <param name="siteId"></param>
/// <returns></returns>
Task<List<ModuleDefinition>> GetModuleDefinitionsAsync(int siteId); Task<List<ModuleDefinition>> GetModuleDefinitionsAsync(int siteId);
/// <summary>
/// Returns a specific module definition
/// </summary>
/// <param name="moduleDefinitionId"></param>
/// <param name="siteId"></param>
/// <returns></returns>
Task<ModuleDefinition> GetModuleDefinitionAsync(int moduleDefinitionId, int siteId); Task<ModuleDefinition> GetModuleDefinitionAsync(int moduleDefinitionId, int siteId);
/// <summary>
/// Updates a existing module definition
/// </summary>
/// <param name="moduleDefinition"></param>
/// <returns></returns>
Task UpdateModuleDefinitionAsync(ModuleDefinition moduleDefinition); Task UpdateModuleDefinitionAsync(ModuleDefinition moduleDefinition);
/// <summary>
/// Installs all module definitions located in //TODO: 2dm where?
/// </summary>
/// <returns></returns>
Task InstallModuleDefinitionsAsync(); Task InstallModuleDefinitionsAsync();
/// <summary>
/// Deletes a module definition
/// </summary>
/// <param name="moduleDefinitionId"></param>
/// <param name="siteId"></param>
/// <returns></returns>
Task DeleteModuleDefinitionAsync(int moduleDefinitionId, int siteId); Task DeleteModuleDefinitionAsync(int moduleDefinitionId, int siteId);
/// <summary>
/// Creates a new module definition
/// </summary>
/// <param name="moduleDefinition"></param>
/// <returns></returns>
Task<ModuleDefinition> CreateModuleDefinitionAsync(ModuleDefinition moduleDefinition); Task<ModuleDefinition> CreateModuleDefinitionAsync(ModuleDefinition moduleDefinition);
/// <summary>
/// Returns a list of module definition templates
/// </summary>
/// <returns></returns>
Task<List<Template>> GetModuleDefinitionTemplatesAsync(); Task<List<Template>> GetModuleDefinitionTemplatesAsync();
} }
} }

View File

@ -1,17 +1,62 @@
using Oqtane.Models; using Oqtane.Models;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Oqtane.Services namespace Oqtane.Services
{ {
/// <summary>
/// Service to retreive and store modules (<see cref="Module"/>)
/// </summary>
public interface IModuleService public interface IModuleService
{ {
/// <summary>
/// Returns a list of modules for the given site
/// </summary>
/// <param name="siteId"></param>
/// <returns></returns>
Task<List<Module>> GetModulesAsync(int siteId); Task<List<Module>> GetModulesAsync(int siteId);
/// <summary>
/// Returns a specific module
/// </summary>
/// <param name="moduleId"></param>
/// <returns></returns>
Task<Module> GetModuleAsync(int moduleId); Task<Module> GetModuleAsync(int moduleId);
/// <summary>
/// Adds a new module
/// </summary>
/// <param name="module"></param>
/// <returns></returns>
Task<Module> AddModuleAsync(Module module); Task<Module> AddModuleAsync(Module module);
/// <summary>
/// Updates an existing module
/// </summary>
/// <param name="module"></param>
/// <returns></returns>
Task<Module> UpdateModuleAsync(Module module); Task<Module> UpdateModuleAsync(Module module);
/// <summary>
/// Deletes a module
/// </summary>
/// <param name="moduleId"></param>
/// <returns></returns>
Task DeleteModuleAsync(int moduleId); Task DeleteModuleAsync(int moduleId);
/// <summary>
/// Imports a module
/// </summary>
/// <param name="moduleId"></param>
/// <param name="content">module in JSON format</param>
/// <returns></returns>
Task<bool> ImportModuleAsync(int moduleId, string content); Task<bool> ImportModuleAsync(int moduleId, string content);
/// <summary>
/// Exports a given module
/// </summary>
/// <param name="moduleId"></param>
/// <returns>module in JSON</returns>
Task<string> ExportModuleAsync(int moduleId); Task<string> ExportModuleAsync(int moduleId);
} }
} }

View File

@ -1,19 +1,49 @@
using Oqtane.Models; using Oqtane.Models;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Oqtane.Services namespace Oqtane.Services
{ {
/// <summary>
/// Service to store and retreive notifications (<see cref="Notification"/>)
/// </summary>
public interface INotificationService public interface INotificationService
{ {
/// <summary>
/// Return a list of notifications
/// </summary>
/// <param name="siteId"></param>
/// <param name="direction"></param>
/// <param name="userId"></param>
/// <returns></returns>
Task<List<Notification>> GetNotificationsAsync(int siteId, string direction, int userId); Task<List<Notification>> GetNotificationsAsync(int siteId, string direction, int userId);
/// <summary>
/// Returns a specific notifications
/// </summary>
/// <param name="notificationId"></param>
/// <returns></returns>
Task<Notification> GetNotificationAsync(int notificationId); Task<Notification> GetNotificationAsync(int notificationId);
/// <summary>
/// Creates a new notification
/// </summary>
/// <param name="notification"></param>
/// <returns></returns>
Task<Notification> AddNotificationAsync(Notification notification); Task<Notification> AddNotificationAsync(Notification notification);
/// <summary>
/// Updates a existing notification
/// </summary>
/// <param name="notification"></param>
/// <returns></returns>
Task<Notification> UpdateNotificationAsync(Notification notification); Task<Notification> UpdateNotificationAsync(Notification notification);
/// <summary>
/// Deletes a notification
/// </summary>
/// <param name="notificationId"></param>
/// <returns></returns>
Task DeleteNotificationAsync(int notificationId); Task DeleteNotificationAsync(int notificationId);
} }
} }

View File

@ -4,11 +4,50 @@ using System.Threading.Tasks;
namespace Oqtane.Services namespace Oqtane.Services
{ {
/// <summary>
/// Service to manage packages (<see cref="Package"/>)
/// </summary>
public interface IPackageService public interface IPackageService
{ {
/// <summary>
/// Returns a list of packages matching the given parameters
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
Task<List<Package>> GetPackagesAsync(string type); Task<List<Package>> GetPackagesAsync(string type);
Task<List<Package>> GetPackagesAsync(string type, string search);
/// <summary>
/// Returns a list of packages matching the given parameters
/// </summary>
/// <param name="type"></param>
/// <param name="search"></param>
/// <param name="price"></param>
/// <param name="package"></param>
/// <returns></returns>
Task<List<Package>> GetPackagesAsync(string type, string search, string price, string package);
/// <summary>
/// Returns a specific package
/// </summary>
/// <param name="packageId"></param>
/// <param name="version"></param>
/// <returns></returns>
Task<Package> GetPackageAsync(string packageId, string version);
/// <summary>
/// Downloads a specific package as .nupkg file
/// </summary>
/// <param name="packageId"></param>
/// <param name="version"></param>
/// <param name="folder"></param>
/// <returns></returns>
Task DownloadPackageAsync(string packageId, string version, string folder); Task DownloadPackageAsync(string packageId, string version, string folder);
/// <summary>
/// Installs all packages located in //TODO: 2dm where?
/// </summary>
/// <returns></returns>
Task InstallPackagesAsync(); Task InstallPackagesAsync();
} }
} }

View File

@ -1,15 +1,56 @@
using Oqtane.Models; using Oqtane.Models;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Oqtane.Services namespace Oqtane.Services
{ {
/// <summary>
/// Service to store and retreive a <see cref="PageModule"/>
/// </summary>
public interface IPageModuleService public interface IPageModuleService
{ {
/// <summary>
/// Returns a specific page module
/// </summary>
/// <param name="pageModuleId"></param>
/// <returns></returns>
Task<PageModule> GetPageModuleAsync(int pageModuleId); Task<PageModule> GetPageModuleAsync(int pageModuleId);
/// <summary>
/// Return a specific page module
/// </summary>
/// <param name="pageId"></param>
/// <param name="moduleId"></param>
/// <returns></returns>
Task<PageModule> GetPageModuleAsync(int pageId, int moduleId); Task<PageModule> GetPageModuleAsync(int pageId, int moduleId);
/// <summary>
/// Creates a new page module
/// </summary>
/// <param name="pageModule"></param>
/// <returns></returns>
Task<PageModule> AddPageModuleAsync(PageModule pageModule); Task<PageModule> AddPageModuleAsync(PageModule pageModule);
/// <summary>
/// Updates a existing page module
/// </summary>
/// <param name="pageModule"></param>
/// <returns></returns>
Task<PageModule> UpdatePageModuleAsync(PageModule pageModule); Task<PageModule> UpdatePageModuleAsync(PageModule pageModule);
/// <summary>
/// Updates order of all page modules in the given pane
/// </summary>
/// <param name="pageId"></param>
/// <param name="pane"></param>
/// <returns></returns>
Task UpdatePageModuleOrderAsync(int pageId, string pane); Task UpdatePageModuleOrderAsync(int pageId, string pane);
/// <summary>
/// Deletes a page module
/// </summary>
/// <param name="pageModuleId"></param>
/// <returns></returns>
Task DeletePageModuleAsync(int pageModuleId); Task DeletePageModuleAsync(int pageModuleId);
} }
} }

View File

@ -1,19 +1,79 @@
using Oqtane.Models; using Oqtane.Models;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Oqtane.Services namespace Oqtane.Services
{ {
/// <summary>
/// Services to store and retrieve a <see cref="Page"/>
/// </summary>
public interface IPageService public interface IPageService
{ {
/// <summary>
/// Retuns a list of pages
/// </summary>
/// <param name="siteId"></param>
/// <returns></returns>
Task<List<Page>> GetPagesAsync(int siteId); Task<List<Page>> GetPagesAsync(int siteId);
/// <summary>
/// Returns a specific page
/// </summary>
/// <param name="pageId"></param>
/// <returns></returns>
Task<Page> GetPageAsync(int pageId); Task<Page> GetPageAsync(int pageId);
/// <summary>
/// Returns a specific page personalized for the given user
/// </summary>
/// <param name="pageId"></param>
/// <param name="userId"></param>
/// <returns></returns>
Task<Page> GetPageAsync(int pageId, int userId); Task<Page> GetPageAsync(int pageId, int userId);
/// <summary>
/// Returns a specific page by its defined path
/// </summary>
/// <param name="path"></param>
/// <param name="siteId"></param>
/// <returns></returns>
Task<Page> GetPageAsync(string path, int siteId); Task<Page> GetPageAsync(string path, int siteId);
/// <summary>
/// Adds a new page
/// </summary>
/// <param name="page"></param>
/// <returns></returns>
Task<Page> AddPageAsync(Page page); Task<Page> AddPageAsync(Page page);
/// <summary>
/// Adds a new page
/// </summary>
/// <param name="page"></param>
/// <returns></returns>
Task<Page> AddPageAsync(int pageId, int userId); Task<Page> AddPageAsync(int pageId, int userId);
/// <summary>
/// Updates a existing page
/// </summary>
/// <param name="page"></param>
/// <returns></returns>
Task<Page> UpdatePageAsync(Page page); Task<Page> UpdatePageAsync(Page page);
/// <summary>
/// Updates order of all page modules in the given parent
/// </summary>
/// <param name="siteId"></param>
/// <param name="pageId"></param>
/// <param name="parentId"></param>
/// <returns></returns>
Task UpdatePageOrderAsync(int siteId, int pageId, int? parentId); Task UpdatePageOrderAsync(int siteId, int pageId, int? parentId);
/// <summary>
/// Deletes a page
/// </summary>
/// <param name="pageId"></param>
/// <returns></returns>
Task DeletePageAsync(int pageId); Task DeletePageAsync(int pageId);
} }
} }

View File

@ -1,19 +1,48 @@
using Oqtane.Models; using Oqtane.Models;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Oqtane.Services namespace Oqtane.Services
{ {
/// <summary>
/// Service to store and retreive <see cref="Profile"/> entries
/// </summary>
public interface IProfileService public interface IProfileService
{ {
/// <summary>
/// Returns a list of profile entries
/// </summary>
/// <param name="siteId"></param>
/// <returns></returns>
Task<List<Profile>> GetProfilesAsync(int siteId); Task<List<Profile>> GetProfilesAsync(int siteId);
/// <summary>
/// Returns a specific profile entry
/// </summary>
/// <param name="profileId"></param>
/// <returns></returns>
Task<Profile> GetProfileAsync(int profileId); Task<Profile> GetProfileAsync(int profileId);
/// <summary>
/// Creates a new profile entry
/// </summary>
/// <param name="profile"></param>
/// <returns></returns>
Task<Profile> AddProfileAsync(Profile profile); Task<Profile> AddProfileAsync(Profile profile);
/// <summary>
/// Updates an existing profile entry
/// </summary>
/// <param name="profile"></param>
/// <returns></returns>
Task<Profile> UpdateProfileAsync(Profile profile); Task<Profile> UpdateProfileAsync(Profile profile);
/// <summary>
/// Deletes a profile entry
/// </summary>
/// <param name="profileId"></param>
/// <returns></returns>
Task DeleteProfileAsync(int profileId); Task DeleteProfileAsync(int profileId);
} }
} }

View File

@ -1,55 +1,184 @@
using Oqtane.Models; using Oqtane.Models;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Oqtane.Services namespace Oqtane.Services
{ {
/// <summary>
/// Service to manage <see cref="Setting"/>s
/// </summary>
public interface ISettingService public interface ISettingService
{ {
/// <summary>
/// Returns a key-value dictionary of all tenant settings
/// </summary>
/// <returns></returns>
Task<Dictionary<string, string>> GetTenantSettingsAsync(); Task<Dictionary<string, string>> GetTenantSettingsAsync();
/// <summary>
/// Updates a tenant setting
/// </summary>
/// <param name="tenantSettings"></param>
/// <returns></returns>
Task UpdateTenantSettingsAsync(Dictionary<string, string> tenantSettings); Task UpdateTenantSettingsAsync(Dictionary<string, string> tenantSettings);
/// <summary>
/// Returns a key-value dictionary of all site settings for the given site
/// </summary>
/// <param name="siteId"></param>
/// <returns></returns>
Task<Dictionary<string, string>> GetSiteSettingsAsync(int siteId); Task<Dictionary<string, string>> GetSiteSettingsAsync(int siteId);
/// <summary>
/// Updates a site setting
/// </summary>
/// <param name="siteSettings"></param>
/// <param name="siteId"></param>
/// <returns></returns>
Task UpdateSiteSettingsAsync(Dictionary<string, string> siteSettings, int siteId); Task UpdateSiteSettingsAsync(Dictionary<string, string> siteSettings, int siteId);
/// <summary>
/// Returns a key-value dictionary of all page settings for the given page
/// </summary>
/// <param name="pageId"></param>
/// <returns></returns>
Task<Dictionary<string, string>> GetPageSettingsAsync(int pageId); Task<Dictionary<string, string>> GetPageSettingsAsync(int pageId);
/// <summary>
/// Updates a page setting
/// </summary>
/// <param name="pageSettings"></param>
/// <param name="pageId"></param>
/// <returns></returns>
Task UpdatePageSettingsAsync(Dictionary<string, string> pageSettings, int pageId); Task UpdatePageSettingsAsync(Dictionary<string, string> pageSettings, int pageId);
/// <summary>
/// Returns a key-value dictionary of all page module settings for the given page module
/// </summary>
/// <param name="pageId"></param>
/// <returns></returns>
Task<Dictionary<string, string>> GetPageModuleSettingsAsync(int pageModuleId); Task<Dictionary<string, string>> GetPageModuleSettingsAsync(int pageModuleId);
/// <summary>
/// Updates a page module setting
/// </summary>
/// <param name="pageModuleSettings"></param>
/// <param name="pageModuleId"></param>
/// <returns></returns>
Task UpdatePageModuleSettingsAsync(Dictionary<string, string> pageModuleSettings, int pageModuleId); Task UpdatePageModuleSettingsAsync(Dictionary<string, string> pageModuleSettings, int pageModuleId);
/// <summary>
/// Returns a key-value dictionary of all module settings for the given module
/// </summary>
/// <param name="pageId"></param>
/// <returns></returns>
Task<Dictionary<string, string>> GetModuleSettingsAsync(int moduleId); Task<Dictionary<string, string>> GetModuleSettingsAsync(int moduleId);
/// <summary>
/// Updates a module setting
/// </summary>
/// <param name="moduleSettings"></param>
/// <param name="moduleId"></param>
/// <returns></returns>
Task UpdateModuleSettingsAsync(Dictionary<string, string> moduleSettings, int moduleId); Task UpdateModuleSettingsAsync(Dictionary<string, string> moduleSettings, int moduleId);
/// <summary>
/// Returns a key-value dictionary of all user settings for the given user
/// </summary>
/// <param name="pageId"></param>
/// <returns></returns>
Task<Dictionary<string, string>> GetUserSettingsAsync(int userId); Task<Dictionary<string, string>> GetUserSettingsAsync(int userId);
/// <summary>
/// Updates a user setting
/// </summary>
/// <param name="userSettings"></param>
/// <param name="userId"></param>
/// <returns></returns>
Task UpdateUserSettingsAsync(Dictionary<string, string> userSettings, int userId); Task UpdateUserSettingsAsync(Dictionary<string, string> userSettings, int userId);
/// <summary>
/// Returns a key-value dictionary of all folder settings for the given folder
/// </summary>
/// <param name="pageId"></param>
/// <returns></returns>
Task<Dictionary<string, string>> GetFolderSettingsAsync(int folderId); Task<Dictionary<string, string>> GetFolderSettingsAsync(int folderId);
/// <summary>
/// Updates a folder setting
/// </summary>
/// <param name="folderSettings"></param>
/// <param name="folderId"></param>
/// <returns></returns>
Task UpdateFolderSettingsAsync(Dictionary<string, string> folderSettings, int folderId); Task UpdateFolderSettingsAsync(Dictionary<string, string> folderSettings, int folderId);
/// <summary>
/// Returns a key-value dictionary of all settings for the given entityName
/// </summary>
/// <param name="pageId"></param>
/// <returns></returns>
Task<Dictionary<string, string>> GetSettingsAsync(string entityName, int entityId); Task<Dictionary<string, string>> GetSettingsAsync(string entityName, int entityId);
/// <summary>
/// Updates settings for a given entityName and Id
/// </summary>
/// <param name="settings"></param>
/// <param name="entityName"></param>
/// <param name="entityId"></param>
/// <returns></returns>
Task UpdateSettingsAsync(Dictionary<string, string> settings, string entityName, int entityId); Task UpdateSettingsAsync(Dictionary<string, string> settings, string entityName, int entityId);
/// <summary>
/// Returns a specific setting
/// </summary>
/// <param name="settingId"></param>
/// <returns></returns>
Task<Setting> GetSettingAsync(int settingId); Task<Setting> GetSettingAsync(int settingId);
/// <summary>
/// Creates a new setting
/// </summary>
/// <param name="setting"></param>
/// <returns></returns>
Task<Setting> AddSettingAsync(Setting setting); Task<Setting> AddSettingAsync(Setting setting);
/// <summary>
/// Updates a existing setting
/// </summary>
/// <param name="setting"></param>
/// <returns></returns>
Task<Setting> UpdateSettingAsync(Setting setting); Task<Setting> UpdateSettingAsync(Setting setting);
/// <summary>
/// Deletes a setting
/// </summary>
/// <param name="settingId"></param>
/// <returns></returns>
Task DeleteSettingAsync(int settingId); Task DeleteSettingAsync(int settingId);
/// <summary>
/// Gets the value of the given settingName (key) from the given key-value dictionary
/// </summary>
/// <param name="settings"></param>
/// <param name="settingName"></param>
/// <param name="defaultValue"></param>
/// <returns></returns>
string GetSetting(Dictionary<string, string> settings, string settingName, string defaultValue); string GetSetting(Dictionary<string, string> settings, string settingName, string defaultValue);
/// <summary>
/// Sets the value of the given settingName (key) in the given key-value dictionary
/// </summary>
/// <param name="settings"></param>
/// <param name="settingName"></param>
/// <param name="defaultValue"></param>
/// <returns></returns>
Dictionary<string, string> SetSetting(Dictionary<string, string> settings, string settingName, string settingValue); Dictionary<string, string> SetSetting(Dictionary<string, string> settings, string settingName, string settingValue);
}
Dictionary<string, string> SetSetting(Dictionary<string, string> settings, string settingName, string settingValue, bool isPublic);
Dictionary<string, string> MergeSettings(Dictionary<string, string> settings1, Dictionary<string, string> settings2);
}
} }

View File

@ -1,3 +1,4 @@
using Oqtane.Documentation;
using Oqtane.Models; using Oqtane.Models;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -5,18 +6,47 @@ using System.Threading.Tasks;
namespace Oqtane.Services namespace Oqtane.Services
{ {
/// <summary>
/// Service to store and retreive <see cref="Site"/> entries
/// </summary>
public interface ISiteService public interface ISiteService
{ {
/// <summary>
/// Returns a list of sites
/// </summary>
/// <returns></returns>
Task<List<Site>> GetSitesAsync(); Task<List<Site>> GetSitesAsync();
/// <summary>
/// Returns a specific site
/// </summary>
/// <param name="siteId"></param>
/// <returns></returns>
Task<Site> GetSiteAsync(int siteId); Task<Site> GetSiteAsync(int siteId);
/// <summary>
/// Creates a new site
/// </summary>
/// <param name="site"></param>
/// <returns></returns>
Task<Site> AddSiteAsync(Site site); Task<Site> AddSiteAsync(Site site);
/// <summary>
/// Updates an existing site
/// </summary>
/// <param name="site"></param>
/// <returns></returns>
Task<Site> UpdateSiteAsync(Site site); Task<Site> UpdateSiteAsync(Site site);
/// <summary>
/// Deletes a site
/// </summary>
/// <param name="siteId"></param>
/// <returns></returns>
Task DeleteSiteAsync(int siteId); Task DeleteSiteAsync(int siteId);
[PrivateApi]
[Obsolete("This method is deprecated.", false)] [Obsolete("This method is deprecated.", false)]
void SetAlias(Alias alias); void SetAlias(Alias alias);
} }

View File

@ -1,11 +1,18 @@
using Oqtane.Models; using Oqtane.Models;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Oqtane.Services namespace Oqtane.Services
{ {
/// <summary>
/// Service to retreive <see cref="SiteTemplate"/> entries
/// </summary>
public interface ISiteTemplateService public interface ISiteTemplateService
{ {
/// <summary>
/// Returns a list of site templates
/// </summary>
/// <returns></returns>
Task<List<SiteTemplate>> GetSiteTemplatesAsync(); Task<List<SiteTemplate>> GetSiteTemplatesAsync();
} }
} }

View File

@ -1,10 +1,18 @@
using Oqtane.Models; using Oqtane.Models;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Oqtane.Services namespace Oqtane.Services
{ {
/// <summary>
/// Service to execute a <see cref="SqlQuery"/> against the backend database
/// </summary>
public interface ISqlService public interface ISqlService
{ {
/// <summary>
/// Executes a sql query and returns its result
/// </summary>
/// <param name="sqlquery"></param>
/// <returns></returns>
Task<SqlQuery> ExecuteQueryAsync(SqlQuery sqlquery); Task<SqlQuery> ExecuteQueryAsync(SqlQuery sqlquery);
} }
} }

View File

@ -3,10 +3,22 @@ using System.Threading.Tasks;
namespace Oqtane.Services namespace Oqtane.Services
{ {
/// <summary>
/// Service to retrieve and update system information.
/// </summary>
public interface ISystemService public interface ISystemService
{ {
/// <summary>
/// returns a key-value directory with the current system information (os-version, clr-version, etc.)
/// </summary>
/// <returns></returns>
Task<Dictionary<string, string>> GetSystemInfoAsync(); Task<Dictionary<string, string>> GetSystemInfoAsync();
/// <summary>
/// Updates system information
/// </summary>
/// <param name="settings"></param>
/// <returns></returns>
Task UpdateSystemInfoAsync(Dictionary<string, string> settings); Task UpdateSystemInfoAsync(Dictionary<string, string> settings);
} }
} }

View File

@ -4,15 +4,65 @@ using System.Threading.Tasks;
namespace Oqtane.Services namespace Oqtane.Services
{ {
/// <summary>
/// Service to manage <see cref="Theme"/> entries
/// </summary>
public interface IThemeService public interface IThemeService
{ {
/// <summary>
/// Returns a list of available themes
/// </summary>
/// <returns></returns>
Task<List<Theme>> GetThemesAsync(); Task<List<Theme>> GetThemesAsync();
/// <summary>
/// Returns a list of <see cref="ThemeControl"/>s from the given themes
/// </summary>
/// <param name="themes"></param>
/// <returns></returns>
List<ThemeControl> GetThemeControls(List<Theme> themes); List<ThemeControl> GetThemeControls(List<Theme> themes);
/// <summary>
/// Returns a list of layouts (<see cref="ThemeControl"/>) from the given themes with a matching theme name
/// </summary>
/// <param name="themes"></param>
/// <param name="themeName"></param>
/// <returns></returns>
List<ThemeControl> GetLayoutControls(List<Theme> themes, string themeName); List<ThemeControl> GetLayoutControls(List<Theme> themes, string themeName);
/// <summary>
/// Returns a list of containers (<see cref="ThemeControl"/>) from the given themes with a matching theme name
/// </summary>
/// <param name="themes"></param>
/// <param name="themeName"></param>
/// <returns></returns>
List<ThemeControl> GetContainerControls(List<Theme> themes, string themeName); List<ThemeControl> GetContainerControls(List<Theme> themes, string themeName);
/// <summary>
/// Installs all themes located in //TODO: 2dm where?
/// </summary>
/// <returns></returns>
Task InstallThemesAsync(); Task InstallThemesAsync();
/// <summary>
/// Deletes a theme
/// </summary>
/// <param name="themeName"></param>
/// <returns></returns>
Task DeleteThemeAsync(string themeName); Task DeleteThemeAsync(string themeName);
/// <summary>
/// Creates a new theme
/// </summary>
/// <param name="theme"></param>
/// <returns></returns>
Task<Theme> CreateThemeAsync(Theme theme); Task<Theme> CreateThemeAsync(Theme theme);
/// <summary>
/// Returns a list of theme templates (<see cref="Template"/>)
/// </summary>
/// <returns></returns>
Task<List<Template>> GetThemeTemplatesAsync(); Task<List<Template>> GetThemeTemplatesAsync();
} }
} }

View File

@ -22,12 +22,17 @@ namespace Oqtane.Services
public async Task<List<Package>> GetPackagesAsync(string type) public async Task<List<Package>> GetPackagesAsync(string type)
{ {
return await GetPackagesAsync(type, ""); return await GetPackagesAsync(type, "", "", "");
} }
public async Task<List<Package>> GetPackagesAsync(string type, string search) public async Task<List<Package>> GetPackagesAsync(string type, string search, string price, string package)
{ {
return await GetJsonAsync<List<Package>>($"{Apiurl}?type={type}&search={WebUtility.UrlEncode(search)}"); return await GetJsonAsync<List<Package>>($"{Apiurl}?type={type}&search={WebUtility.UrlEncode(search)}&price={price}&package={package}");
}
public async Task<Package> GetPackageAsync(string packageId, string version)
{
return await PostJsonAsync<Package>($"{Apiurl}?packageid={packageId}&version={version}", null);
} }
public async Task DownloadPackageAsync(string packageId, string version, string folder) public async Task DownloadPackageAsync(string packageId, string version, string folder)

View File

@ -47,6 +47,7 @@ namespace Oqtane.Services
{ {
return await PutJsonAsync<Role>($"{Apiurl}/{role.RoleId}", role); return await PutJsonAsync<Role>($"{Apiurl}/{role.RoleId}", role);
} }
public async Task DeleteRoleAsync(int roleId) public async Task DeleteRoleAsync(int roleId)
{ {
await DeleteAsync($"{Apiurl}/{roleId}"); await DeleteAsync($"{Apiurl}/{roleId}");

View File

@ -109,21 +109,34 @@ namespace Oqtane.Services
foreach (KeyValuePair<string, string> kvp in settings) foreach (KeyValuePair<string, string> kvp in settings)
{ {
Setting setting = settingsList.FirstOrDefault(item => item.SettingName.Equals(kvp.Key,StringComparison.OrdinalIgnoreCase)); string value = kvp.Value;
bool ispublic = false;
if (value.StartsWith("[Public]"))
{
if (entityName == EntityNames.Site)
{
ispublic = true;
}
value = value.Substring(8); // remove [Public]
}
Setting setting = settingsList.FirstOrDefault(item => item.SettingName.Equals(kvp.Key, StringComparison.OrdinalIgnoreCase));
if (setting == null) if (setting == null)
{ {
setting = new Setting(); setting = new Setting();
setting.EntityName = entityName; setting.EntityName = entityName;
setting.EntityId = entityId; setting.EntityId = entityId;
setting.SettingName = kvp.Key; setting.SettingName = kvp.Key;
setting.SettingValue = kvp.Value; setting.SettingValue = value;
setting.IsPublic = ispublic;
setting = await AddSettingAsync(setting); setting = await AddSettingAsync(setting);
} }
else else
{ {
if (setting.SettingValue != kvp.Value) if (setting.SettingValue != kvp.Value)
{ {
setting.SettingValue = kvp.Value; setting.SettingValue = value;
setting.IsPublic = ispublic;
setting = await UpdateSettingAsync(setting); setting = await UpdateSettingAsync(setting);
} }
} }
@ -163,11 +176,17 @@ namespace Oqtane.Services
} }
public Dictionary<string, string> SetSetting(Dictionary<string, string> settings, string settingName, string settingValue) public Dictionary<string, string> SetSetting(Dictionary<string, string> settings, string settingName, string settingValue)
{
return SetSetting(settings, settingName, settingValue, false);
}
public Dictionary<string, string> SetSetting(Dictionary<string, string> settings, string settingName, string settingValue, bool isPublic)
{ {
if (settings == null) if (settings == null)
{ {
settings = new Dictionary<string, string>(); settings = new Dictionary<string, string>();
} }
settingValue = (isPublic) ? "[Public]" + settingValue : settingValue;
if (settings.ContainsKey(settingName)) if (settings.ContainsKey(settingName))
{ {
settings[settingName] = settingValue; settings[settingName] = settingValue;
@ -178,5 +197,28 @@ namespace Oqtane.Services
} }
return settings; return settings;
} }
public Dictionary<string, string> MergeSettings(Dictionary<string, string> settings1, Dictionary<string, string> settings2)
{
if (settings1 == null)
{
settings1 = new Dictionary<string, string>();
}
if (settings2 != null)
{
foreach (var setting in settings2)
{
if (settings1.ContainsKey(setting.Key))
{
settings1[setting.Key] = setting.Value;
}
else
{
settings1.Add(setting.Key, setting.Value);
}
}
}
return settings1;
}
} }
} }

View File

@ -96,7 +96,7 @@ namespace Oqtane.Themes.Controls
{ {
PageModule pagemodule = await PageModuleService.GetPageModuleAsync(ModuleState.PageModuleId); PageModule pagemodule = await PageModuleService.GetPageModuleAsync(ModuleState.PageModuleId);
string url = NavigateUrl(); string url = NavigateUrl(true);
if (action.Action != null) if (action.Action != null)
{ {
@ -115,7 +115,7 @@ namespace Oqtane.Themes.Controls
await PageModuleService.UpdatePageModuleAsync(pagemodule); await PageModuleService.UpdatePageModuleAsync(pagemodule);
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane); await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane);
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, oldPane); await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, oldPane);
return NavigateUrl(url, true); return url;
} }
private async Task<string> DeleteModule(string url, PageModule pagemodule) private async Task<string> DeleteModule(string url, PageModule pagemodule)
@ -123,7 +123,7 @@ namespace Oqtane.Themes.Controls
pagemodule.IsDeleted = true; pagemodule.IsDeleted = true;
await PageModuleService.UpdatePageModuleAsync(pagemodule); await PageModuleService.UpdatePageModuleAsync(pagemodule);
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane); await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane);
return NavigateUrl(url, true); return url;
} }
private async Task<string> Settings(string url, PageModule pagemodule) private async Task<string> Settings(string url, PageModule pagemodule)
@ -133,7 +133,7 @@ namespace Oqtane.Themes.Controls
return url; return url;
} }
private async Task<string> Publish(string s, PageModule pagemodule) private async Task<string> Publish(string url, PageModule pagemodule)
{ {
var permissions = UserSecurity.GetPermissionStrings(pagemodule.Module.Permissions); var permissions = UserSecurity.GetPermissionStrings(pagemodule.Module.Permissions);
foreach (var permissionstring in permissions) foreach (var permissionstring in permissions)
@ -148,10 +148,10 @@ namespace Oqtane.Themes.Controls
} }
pagemodule.Module.Permissions = UserSecurity.SetPermissionStrings(permissions); pagemodule.Module.Permissions = UserSecurity.SetPermissionStrings(permissions);
await ModuleService.UpdateModuleAsync(pagemodule.Module); await ModuleService.UpdateModuleAsync(pagemodule.Module);
return NavigateUrl(s, true); return url;
} }
private async Task<string> Unpublish(string s, PageModule pagemodule) private async Task<string> Unpublish(string url, PageModule pagemodule)
{ {
var permissions = UserSecurity.GetPermissionStrings(pagemodule.Module.Permissions); var permissions = UserSecurity.GetPermissionStrings(pagemodule.Module.Permissions);
foreach (var permissionstring in permissions) foreach (var permissionstring in permissions)
@ -166,39 +166,39 @@ namespace Oqtane.Themes.Controls
} }
pagemodule.Module.Permissions = UserSecurity.SetPermissionStrings(permissions); pagemodule.Module.Permissions = UserSecurity.SetPermissionStrings(permissions);
await ModuleService.UpdateModuleAsync(pagemodule.Module); await ModuleService.UpdateModuleAsync(pagemodule.Module);
return NavigateUrl(s, true); return url;
} }
private async Task<string> MoveTop(string s, PageModule pagemodule) private async Task<string> MoveTop(string url, PageModule pagemodule)
{ {
pagemodule.Order = 0; pagemodule.Order = 0;
await PageModuleService.UpdatePageModuleAsync(pagemodule); await PageModuleService.UpdatePageModuleAsync(pagemodule);
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane); await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane);
return NavigateUrl(s, true); return url;
} }
private async Task<string> MoveBottom(string s, PageModule pagemodule) private async Task<string> MoveBottom(string url, PageModule pagemodule)
{ {
pagemodule.Order = int.MaxValue; pagemodule.Order = int.MaxValue;
await PageModuleService.UpdatePageModuleAsync(pagemodule); await PageModuleService.UpdatePageModuleAsync(pagemodule);
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane); await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane);
return NavigateUrl(s, true); return url;
} }
private async Task<string> MoveUp(string s, PageModule pagemodule) private async Task<string> MoveUp(string url, PageModule pagemodule)
{ {
pagemodule.Order -= 3; pagemodule.Order -= 3;
await PageModuleService.UpdatePageModuleAsync(pagemodule); await PageModuleService.UpdatePageModuleAsync(pagemodule);
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane); await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane);
return NavigateUrl(s, true); return url;
} }
private async Task<string> MoveDown(string s, PageModule pagemodule) private async Task<string> MoveDown(string url, PageModule pagemodule)
{ {
pagemodule.Order += 3; pagemodule.Order += 3;
await PageModuleService.UpdatePageModuleAsync(pagemodule); await PageModuleService.UpdatePageModuleAsync(pagemodule);
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane); await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane);
return NavigateUrl(s, true); return url;
} }
public class ActionViewModel public class ActionViewModel

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