Compare commits

...

119 Commits

Author SHA1 Message Date
2c56bfd4aa Merge pull request #2565 from oqtane/master
Merge pull request #2564 from oqtane/dev
2023-01-14 14:54:37 -05:00
755615da30 Merge pull request #2564 from oqtane/dev
3.3.1 release
2023-01-14 14:54:18 -05:00
52af015c2f Merge pull request #2563 from sbwalker/dev
prepare for 3.3.1 patch
2023-01-14 14:44:32 -05:00
4cc0060c67 prepare for 3.3.1 patch 2023-01-14 14:45:30 -05:00
eae6d13284 Fix #2561 - set Permission EntityName explicitly to Module when adding module to page from Control Panel 2023-01-14 12:39:34 -05:00
2a20a7cf21 Merge pull request #2562 from sbwalker/dev
Fix #2561 - set Permission EntityName explicitly to Module when adding module to page from Control Panel
2023-01-14 12:38:55 -05:00
27251005ec incorrect url in framework nusepc 2023-01-13 07:50:58 -05:00
90a9b2e5a1 Merge pull request #2560 from sbwalker/dev
incorrect url in framework nusepc
2023-01-13 07:50:04 -05:00
cd1f12e9b4 Merge pull request #2557 from oqtane/master
Merge pull request #2556 from oqtane/dev
2023-01-12 12:50:09 -05:00
734b9b6458 Merge pull request #2556 from oqtane/dev
3.3.0 Release
2023-01-12 12:49:49 -05:00
a120449c8d Update README.md 2023-01-12 12:48:54 -05:00
b005a1da56 Merge pull request #2555 from sbwalker/dev
remove extra info from Body as the From display name is now always set to the user's name
2023-01-12 11:47:32 -05:00
afc75a09d9 remove extra info from Body as the From display name is now always set to the user's name 2023-01-12 11:48:25 -05:00
c6e6c98875 improve notification job 2023-01-12 09:13:13 -05:00
fa1a5ab913 Merge pull request #2554 from sbwalker/dev
improve notification job
2023-01-12 09:12:11 -05:00
b671b590ad change Sql Manager logging level 2023-01-12 08:18:36 -05:00
a46c98eed3 Merge pull request #2553 from sbwalker/dev
change Sql Manager logging level
2023-01-12 08:17:42 -05:00
7ab5ed5bcb copyright year update 2023-01-11 14:40:16 -05:00
5e609edc31 Merge pull request #2551 from sbwalker/dev
copyright year update
2023-01-11 14:39:15 -05:00
ac466429f8 improve release batch file 2023-01-11 10:24:50 -05:00
2804c50c8a Merge pull request #2550 from sbwalker/dev
improve release batch file
2023-01-11 10:24:18 -05:00
c4315c25bc prepare for 3.3.0 release 2023-01-10 14:02:23 -05:00
e2d5fa48cf Merge pull request #2548 from sbwalker/dev
prepare for 3.3.0 release
2023-01-10 14:01:48 -05:00
c2375c897d permission updates 2023-01-10 08:20:32 -05:00
49f181583b Merge pull request #2546 from sbwalker/dev
permission updates
2023-01-10 08:20:27 -05:00
ea463a6548 fix #2534 - added Relay Configured site setting to enable sending from users email address 2023-01-09 16:37:06 -05:00
1a567dbdc1 Merge pull request #2545 from sbwalker/dev
fix #2534 - added Relay Configured site setting to enable sending from users email address
2023-01-09 16:36:30 -05:00
e4ec10ef49 format PermissionNames to be more readable 2023-01-09 15:36:41 -05:00
3db4fc687a Merge pull request #2544 from sbwalker/dev
format PermissionNames to be more readable
2023-01-09 15:35:59 -05:00
e136972cd7 add support for API permissions at the UI layer - including ability to delegate user, role, profile management 2023-01-09 11:38:25 -05:00
7b1b32e16f Merge pull request #2543 from sbwalker/dev
add support for API permissions at the UI layer - including ability to delegate user, role, profile management
2023-01-09 11:37:54 -05:00
1616f94b86 add ability to view error.log in System Info 2023-01-05 10:18:55 -05:00
7043d1f3bf Merge pull request #2542 from sbwalker/dev
add ability to view error.log in System Info
2023-01-05 10:18:10 -05:00
7bebfe1919 fix typo and help text 2023-01-05 09:55:06 -05:00
d33ded0426 Merge pull request #2541 from sbwalker/dev
fix typo and help text
2023-01-05 09:54:23 -05:00
66aa67581f improve dynamic policy registration to handle possible race conditions 2023-01-05 09:43:59 -05:00
3a95c05db9 Merge pull request #2540 from sbwalker/dev
improve dynamic policy registration to handle possible race conditions
2023-01-05 09:43:15 -05:00
67046e9d36 Merge pull request #2535 from thabaum/Readme-Glow-Logo
Update framework root to use the new dark/light theme glow logo
2023-01-04 14:51:39 -05:00
6f2965a5b0 Merge pull request #2537 from leigh-pointer/Pager-Pointer
A change to the Pager bar to set the mouse pointer to pointer
2023-01-04 14:51:29 -05:00
197db449a1 Merge pull request #2539 from sbwalker/dev
include owner in migration tag name in external module template
2023-01-04 14:51:11 -05:00
f4800bb7f0 include owner in migration tag name in external module template 2023-01-04 14:51:55 -05:00
6a213561f6 added an autocomplete component and implemented in permission grid 2023-01-04 14:50:05 -05:00
8f8d31f0c9 Merge pull request #2538 from sbwalker/dev
added an autocomplete component and implemented in permission grid
2023-01-04 14:49:23 -05:00
f8cfdacc26 A change to the Pager bar to set the mouse pointer to pointer
Currently the mouse pointer shows the Selector icon when hoovered over the page number buttons. This is an update changing the icon to the Pointer icon.

Updated the CSS class name to 'app-pager-pointer' in app.css and the Component.
2023-01-03 14:24:17 +01:00
07223e27a4 Add Oqtane Glow Logo for Dark/Light Themes 2022-12-27 10:30:00 -08:00
4c6f46ad17 Remove Dark Logo from Framework Root 2022-12-27 10:28:37 -08:00
39dc288f9c Merge pull request #2533 from sbwalker/dev
fix #2526 - support multiple TabStrip components on a page
2022-12-19 15:57:30 -05:00
467e88ef55 fix #2526 - support multiple TabStrip components on a page 2022-12-19 15:58:04 -05:00
0965db5d57 fix skip pages logic in pager where screen was not being refreshed 2022-12-19 15:11:04 -05:00
c30339d9b8 Merge pull request #2532 from sbwalker/dev
fix skip pages logic in pager where screen was not being refreshed
2022-12-19 15:10:27 -05:00
369adcb173 allow profile management to be delegated 2022-12-19 15:07:38 -05:00
d837b981d4 Merge pull request #2531 from sbwalker/dev
allow profile management to be delegated
2022-12-19 15:07:03 -05:00
f455461c1e Update README.md 2022-12-14 14:54:23 -05:00
0087b188a1 improve null handling 2022-12-07 10:33:25 -05:00
98602f49d8 Merge pull request #2522 from sbwalker/dev
improve null handling
2022-12-07 10:33:21 -05:00
70466e5626 Merge pull request #2521 from sbwalker/dev
fix #2513 - add new methods for deleting a setting and retrieving a list of settings
2022-12-07 08:59:07 -05:00
cc7f98a6fe fix #2513 - add new methods for deleting a setting and retrieving a list of settings 2022-12-07 08:59:03 -05:00
fd13ad1fca initialize API permissions based on default roles 2022-12-06 17:16:51 -05:00
5077f6fbca Merge pull request #2520 from sbwalker/dev
initialize API permissions based on default roles
2022-12-06 17:16:47 -05:00
9b15ce6d29 Merge pull request #2519 from sbwalker/dev
make casing consistent in route template definition and method parameter declation or else Swagger will not be able to resolve
2022-12-06 10:48:57 -05:00
5a8ca24566 make casing consistent in route template definition and method parameter declation or else Swagger will not be able to resolve 2022-12-06 10:48:56 -05:00
d3f982cae1 Merge pull request #2518 from sbwalker/dev
add ModuleControllerBase helper method for validating EntityId
2022-12-05 14:21:15 -05:00
28b58b9048 add ModuleControllerBase helper method for validating EntityId 2022-12-05 14:21:12 -05:00
cb10dde97d added API Management for managing site level entity permissions 2022-12-02 16:42:43 -05:00
2c179867e8 Merge pull request #2517 from sbwalker/dev
added API Management for managing site level entity permissions
2022-12-02 16:42:41 -05:00
44fc6de82b hide connection string by default in SQL Management and provide toggle for display 2022-12-02 08:16:18 -05:00
f671af43cd Merge pull request #2516 from sbwalker/dev
hide connection string by default in SQL Management and provide toggle for display
2022-12-02 08:16:18 -05:00
8c0dc6422e Merge pull request #2515 from sbwalker/dev
fix #2512 - provide guidance about password complexity policy during install, and ensure modified passwords meet complexity policy
2022-12-02 07:42:55 -05:00
c91e285475 fix #2512 - provide guidance about password complexity policy during install, and ensure modified passwords meet complexity policy 2022-12-02 07:42:49 -05:00
642e41530e Merge pull request #2514 from sbwalker/dev
enhance dynamic authorization policies to support default role specification
2022-12-02 07:34:08 -05:00
b09a3ccdae enhance dynamic authorization policies to support default role specification 2022-12-02 07:34:06 -05:00
a1aab62cea Merge pull request #2504 from leigh-pointer/PagerPointer
A change to the Pager bar to set the mouse pointer to pointer
2022-11-23 11:26:57 -05:00
e8b1aca45c Merge pull request #2507 from sbwalker/dev
fix #2502 - invalid logic checking querystring parameter
2022-11-23 11:26:21 -05:00
3de98873d6 fix #2502 - invalid logic checking querystring parameter 2022-11-23 11:26:23 -05:00
c030ede12c Merge pull request #2506 from sbwalker/dev
fix #2503 - generate password using CultureInfo.InvariantCulture to ensure it satisfies password complexity criteria
2022-11-23 11:11:05 -05:00
67f740c264 fix #2503 - generate password using CultureInfo.InvariantCulture to ensure it satisfies password complexity criteria 2022-11-23 11:10:59 -05:00
9b68337047 Merge pull request #2505 from sbwalker/dev
fix #2501 - set default Visibility to Same As Page when adding module to a page
2022-11-23 10:51:50 -05:00
2bae971b92 fix #2501 - set default Visibility to Same As Page when adding modules to a page 2022-11-23 10:51:37 -05:00
c5c5fd859f A change to the Pager bar to set the mouse pointer to pointer
Currently the mouse pointer shows the Selector icon when hoovered over the page number buttons.  This is an update changing the icon to the Pointer icon.
2022-11-21 09:44:30 +01:00
15c46fd157 Merge pull request #2496 from sbwalker/dev
Fix #2488 - add ability to include inline script resource definitions in modules and themes
2022-11-12 10:59:25 -05:00
424950bd3e Fix #2488 - add ability to include inline script resource definitions in modules and themes 2022-11-12 10:58:58 -05:00
39a9968971 Merge pull request #2493 from sbwalker/dev
fix JS Interop methods for includeScript and includeMeta
2022-11-10 14:19:52 -05:00
075a09f0df fix JS Interop methods for includeScript and includeMeta 2022-11-10 14:19:31 -05:00
26e628e189 Merge pull request #2492 from sbwalker/dev
move UI logic from FileService to FileManager, add progressive retry logic, update file attributes if uploading a new version of a file, clean up temporary artifacts on failure, improve upload efficiency
2022-11-09 21:11:42 -05:00
7489d9d186 move UI logic from FileService to FileManager, add progressive retry logic, update file attributes if uploading a new version of a file, clean up temporary artifacts on failure, improve upload efficiency 2022-11-09 21:11:02 -05:00
7db5b6c7f3 Update README.md 2022-11-08 08:59:49 -05:00
9b7fa8cac2 Update README.md 2022-11-08 08:53:33 -05:00
5028051a34 Update README.md 2022-11-08 08:52:51 -05:00
83b3767df6 Merge pull request #2490 from sbwalker/dev
Scope permissions by SiteId to support entity level authorization as well as improve caching and performance. Optimize GetTenant to use existing cache.
2022-11-07 18:17:04 -05:00
6182b96d16 Scope permissions by SiteId to support entity level authorization as well as improve caching and performance. Optimize GetTenant to use existing cache. 2022-11-07 18:16:32 -05:00
a719382563 Merge pull request #2482 from sbwalker/dev
add support for dynamic authorization policies
2022-11-04 08:08:33 -04:00
2aa6eb90e2 add support for dynamic authorization policies 2022-11-04 08:08:10 -04:00
d2495455cd Merge pull request #2477 from sbwalker/dev
added ETag / 304 Not Modified logic to File server for performance optimization
2022-10-29 10:23:58 -04:00
23d1dd23d1 added ETag / 304 Not Modified logic to File server for performance optimization 2022-10-29 10:23:04 -04:00
573acd6a5d Merge pull request #2476 from sbwalker/dev
fix File Update API to update the file size and image dimensions
2022-10-27 09:39:05 -04:00
40ddbbfbb7 fix File Update API to update the file size and image dimensions 2022-10-27 09:38:26 -04:00
1b488b298b Merge pull request #2475 from sbwalker/dev
remove IDeletable fields from Folder and File entities as they are never set and not used
2022-10-26 17:43:09 -04:00
54b45943db remove IDeletable fields from Folder and File entities as they are never set and not used 2022-10-26 17:42:26 -04:00
89b3ae4c74 Merge pull request #2474 from sbwalker/dev
fix IDeletable code documentation
2022-10-26 17:33:52 -04:00
fe97a76d00 fix IDeletable code documentation 2022-10-26 17:33:17 -04:00
78094f69d7 Merge pull request #2473 from sbwalker/dev
update models to use new ModelBase
2022-10-26 17:27:22 -04:00
4499d55464 update models to use new ModelBase 2022-10-26 17:26:46 -04:00
4c12ca0607 Merge pull request #2472 from sbwalker/dev
introduced a ModelBase to move the IAuditable properties to a base class
2022-10-26 17:12:38 -04:00
1daa9575db introduced a ModelBase to move the IAuditable properties to a base class 2022-10-26 17:12:03 -04:00
f7c9961f8c Merge pull request #2471 from sbwalker/dev
add loading icon to Maui project
2022-10-26 10:06:57 -04:00
e27a625069 add loading icon to Maui project 2022-10-26 09:04:04 -04:00
42b1669cce Merge pull request #2469 from sbwalker/dev
remove Oqtane.Server from external templates as they cause random compilation issues
2022-10-20 14:05:45 -04:00
74571afc9e remove Oqtane.Server from external templates as they cause random compilation issues 2022-10-20 14:04:53 -04:00
f678f11c59 Merge pull request #2468 from sbwalker/dev
add validation message for missing package name
2022-10-20 14:00:56 -04:00
e685252b1d add validation message for missing package name 2022-10-20 14:00:17 -04:00
bb75fcb5a3 Merge pull request #2467 from sbwalker/dev
fix language delete refresh
2022-10-20 13:43:36 -04:00
7653f36f31 fix language delete refresh 2022-10-20 13:42:54 -04:00
0670148064 Merge pull request #2466 from sbwalker/dev
fix #2464 - translation install/upgrade experience
2022-10-20 13:17:06 -04:00
368b900a6e fix #2464 - translation install/upgrade experience 2022-10-20 13:16:18 -04:00
6378a78cbd Update README.md 2022-10-18 08:10:48 -04:00
d3bcdec0f2 Update README.md 2022-10-18 08:10:02 -04:00
82c074ce2e Merge pull request #2463 from sbwalker/dev
replace assembly references with package references
2022-10-18 07:52:33 -04:00
a313e2d386 replace assembly references with package references 2022-10-18 07:51:50 -04:00
578cb2f7e3 Update README.md 2022-10-18 07:43:18 -04:00
162 changed files with 2480 additions and 1839 deletions

View File

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2018-2022 .NET Foundation Copyright (c) 2018-2023 .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

@ -20,13 +20,16 @@
</div> </div>
@code { @code {
private List<Page> _pages; private List<Page> _pages;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous;
protected override void OnInitialized() protected override void OnInitialized()
{ {
var admin = PageState.Pages.FirstOrDefault(item => item.Path == "admin"); var admin = PageState.Pages.FirstOrDefault(item => item.Path == "admin");
_pages = PageState.Pages.Where(item => item.ParentId == admin?.PageId).ToList(); if (admin != null)
{
_pages = PageState.Pages.Where(item => item.ParentId == admin?.PageId).ToList();
}
} }
} }

View File

@ -9,7 +9,7 @@
@inject IStringLocalizer<Add> Localizer @inject IStringLocalizer<Add> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer @inject IStringLocalizer<SharedResources> SharedLocalizer
@if (_supportedCultures == null) @if (_cultures == null)
{ {
<p><em>@SharedLocalizer["Loading"]</em></p> <p><em>@SharedLocalizer["Loading"]</em></p>
} }
@ -17,104 +17,42 @@ else
{ {
<TabStrip> <TabStrip>
<TabPanel Name="Manage" ResourceKey="Manage"> <TabPanel Name="Manage" ResourceKey="Manage">
@if (_availableCultures.Count() == 0) <form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
{ <div class="container">
<ModuleMessage Type="MessageType.Info" Message="@_message"></ModuleMessage> <div class="row mb-1 align-items-center">
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink> <Label Class="col-sm-3" For="translated" HelpText="Specify If You Wish To Select Languages That Have Translations Installed" ResourceKey="Translated">Translated?</Label>
} <div class="col-sm-9">
else <select id="translated" class="form-select" value="@_translated" @onchange="(e => TranslatedChanged(e))" required>
{ <option value="True">@SharedLocalizer["Yes"]</option>
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate> <option value="False">@SharedLocalizer["No"]</option>
<div class="container"> </select>
<div class="row mb-1 align-items-center"> </div>
<Label Class="col-sm-3" For="name" HelpText="Name Of The Language" ResourceKey="Name">Name:</Label> </div>
<div class="col-sm-9"> <div class="row mb-1 align-items-center">
<select id="_code" class="form-select" @bind="@_code" required> <Label Class="col-sm-3" For="name" HelpText="Name Of The Language" ResourceKey="Name">Name:</Label>
@foreach (var culture in _availableCultures) <div class="col-sm-9">
{ <select id="_code" class="form-select" @bind="@_code" required>
<option value="@culture.Name">@(culture.DisplayName + " (" + culture.Name + ")")</option> <option value="-">&lt;@SharedLocalizer["Not Specified"]&gt;</option>
} @foreach (var culture in _cultures)
</select> {
</div> <option value="@culture.Name">@(culture.DisplayName + " (" + culture.Name + ")")</option>
</div> }
<div class="row mb-1 align-items-center"> </select>
<Label Class="col-sm-3" For="default" HelpText="Indicates Whether Or Not This Language Is The Default For The Site" ResourceKey="IsDefault">Default?</Label>
<div class="col-sm-9">
<select id="default" class="form-select" @bind="@_isDefault" required>
<option value="True">@SharedLocalizer["Yes"]</option>
<option value="False">@SharedLocalizer["No"]</option>
</select>
</div>
</div> </div>
</div> </div>
<button type="button" class="btn btn-success" @onclick="SaveLanguage">@SharedLocalizer["Save"]</button> <div class="row mb-1 align-items-center">
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink> <Label Class="col-sm-3" For="default" HelpText="Indicates Whether Or Not This Language Is The Default For The Site" ResourceKey="IsDefault">Default?</Label>
</form> <div class="col-sm-9">
} <select id="default" class="form-select" @bind="@_default" required>
</TabPanel> <option value="True">@SharedLocalizer["Yes"]</option>
<TabPanel Name="Translations" Heading="Translations" ResourceKey="Download" Security="SecurityAccessLevel.Host"> <option value="False">@SharedLocalizer["No"]</option>
<div class="row justify-content-center mb-3"> </select>
<div class="col-sm-6"> </div>
<div class="input-group">
<select id="price" class="form-select custom-select" @onchange="(e => PriceChanged(e))">
<option value="free">@SharedLocalizer["Free"]</option>
<option value="paid">@SharedLocalizer["Paid"]</option>
</select>
<input id="search" class="form-control" placeholder="@SharedLocalizer["Search.Hint"]" @bind="@_search" />
<button type="button" class="btn btn-primary" @onclick="Search">@SharedLocalizer["Search"]</button>
<button type="button" class="btn btn-secondary" @onclick="Reset">@SharedLocalizer["Reset"]</button>
</div> </div>
</div> </div>
</div> <button type="button" class="btn btn-success" @onclick="SaveLanguage">@SharedLocalizer["Save"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
@if (_packages != null) </form>
{
@if (_packages.Count > 0)
{
<Pager Items="@_packages">
<Row>
<td>
<h3 style="display: inline;"><a href="@context.ProductUrl" target="_new">@context.Name</a></h3>&nbsp;&nbsp;by:&nbsp;&nbsp;<strong><a href="@context.OwnerUrl" target="new">@context.Owner</a></strong><br />
@(context.Description.Length > 400 ? (context.Description.Substring(0, 400) + "...") : context.Description)<br />
<strong>@(String.Format("{0:n0}", context.Downloads))</strong> @SharedLocalizer["Search.Downloads"]&nbsp;&nbsp;|&nbsp;&nbsp;
@SharedLocalizer["Search.Released"]: <strong>@context.ReleaseDate.ToString("MMM dd, yyyy")</strong>&nbsp;&nbsp;|&nbsp;&nbsp;
@SharedLocalizer["Search.Version"]: <strong>@context.Version</strong>
@((MarkupString)(!string.IsNullOrEmpty(context.PackageUrl) ? "&nbsp;&nbsp;|&nbsp;&nbsp;" + SharedLocalizer["Search.Source"] + ": <strong>" + new Uri(context.PackageUrl).Host + "</strong>" : ""))
@((MarkupString)(context.TrialPeriod > 0 ? "&nbsp;&nbsp;|&nbsp;&nbsp;<strong>" + context.TrialPeriod + " " + @SharedLocalizer["Trial"] + "</strong>" : ""))
</td>
<td style="width: 1px; vertical-align: middle;">
@if (context.Price != null && !string.IsNullOrEmpty(context.PackageUrl))
{
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
}
</td>
<td style="width: 1px; vertical-align: middle;">
@if (context.Price != null && !string.IsNullOrEmpty(context.PaymentUrl))
{
<a class="btn btn-primary" style="text-decoration: none !important" href="@context.PaymentUrl" target="_new">@context.Price.Value.ToString("$#,##0.00")</a>
}
else
{
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(context.PackageId, context.Version))>@SharedLocalizer["Download"]</button>
}
</td>
</Row>
</Pager>
}
else
{
<br />
<div class="mx-auto text-center">
@Localizer["Search.NoResults"]
</div>
}
<button type="button" class="btn btn-success" @onclick="InstallLanguages">@SharedLocalizer["Install"]</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
<br />
<br />
<ModuleMessage Type="MessageType.Info" Message="@SharedLocalizer["Oqtane.Marketplace"]" />
}
</TabPanel> </TabPanel>
<TabPanel Name="Upload" ResourceKey="Upload" Security="SecurityAccessLevel.Host"> <TabPanel Name="Upload" ResourceKey="Upload" Security="SecurityAccessLevel.Host">
<div class="container"> <div class="container">
@ -125,216 +63,86 @@ else
</div> </div>
</div> </div>
</div> </div>
<button type="button" class="btn btn-success" @onclick="InstallLanguages">@SharedLocalizer["Install"]</button> <button type="button" class="btn btn-success" @onclick="InstallTranslations">@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 ElementReference form;
private bool validated = false; private bool validated = false;
private string _code = string.Empty; private string _translated = "True";
private string _isDefault = "False"; private string _code = "-";
private string _message; private string _default = "False";
private IEnumerable<Culture> _supportedCultures; private List<string> _languages;
private IEnumerable<Culture> _availableCultures; private IEnumerable<Culture> _cultures;
private List<Package> _packages;
private string _price = "free";
private string _search = "";
private string _productname = "";
private string _license = "";
private string _packageid = "";
private string _version = "";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
protected override async Task OnParametersSetAsync() protected override async Task OnParametersSetAsync()
{ {
var languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId); var languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId);
var languagesCodes = languages.Select(l => l.Code).ToList(); _languages = languages.Select(l => l.Code).ToList();
_supportedCultures = await LocalizationService.GetCulturesAsync(); await LoadCultures();
_availableCultures = _supportedCultures.Where(c => !c.Name.Equals(Constants.DefaultCulture) && !languagesCodes.Contains(c.Name)); }
await LoadTranslations();
if (_supportedCultures.Count() == 1) private async Task LoadCultures()
{ {
_message = Localizer["OnlyEnglish"]; _cultures = await LocalizationService.GetCulturesAsync(bool.Parse(_translated));
} _cultures = _cultures.Where(c => !c.Name.Equals(Constants.DefaultCulture) && !_languages.Contains(c.Name));
else if (_availableCultures.Count() == 0) _code = "-";
{ }
_message = Localizer["AllLanguages"];
}
}
private async Task LoadTranslations() private async void TranslatedChanged(ChangeEventArgs e)
{ {
_packages = await PackageService.GetPackagesAsync("translation", _search, _price, ""); _translated = (string)e.Value;
} await LoadCultures();
StateHasChanged();
}
private async void PriceChanged(ChangeEventArgs e) private async Task SaveLanguage()
{ {
try validated = true;
{ var interop = new Interop(JSRuntime);
_price = (string)e.Value; if (await interop.FormValid(form) && _code != "-")
_search = ""; {
await LoadTranslations(); var language = new Language
StateHasChanged(); {
} SiteId = PageState.Page.SiteId,
catch (Exception ex) Name = CultureInfo.GetCultureInfo(_code).DisplayName,
{ Code = _code,
await logger.LogError(ex, "Error On PriceChanged"); IsDefault = (_default == null ? false : Boolean.Parse(_default))
} };
}
private async Task Search() try
{ {
try language = await LanguageService.AddLanguageAsync(language);
{
await LoadTranslations();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error On Search");
}
}
private async Task Reset() if (language.IsDefault)
{ {
try await SetCultureAsync(language.Code);
{ }
_search = "";
await LoadTranslations();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error On Reset");
}
}
private async Task SaveLanguage() await logger.LogInformation("Language Added {Language}", language);
{
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{
var language = new Language
{
SiteId = PageState.Page.SiteId,
Name = CultureInfo.GetCultureInfo(_code).DisplayName,
Code = _code,
IsDefault = (_isDefault == null ? false : Boolean.Parse(_isDefault))
};
try NavigationManager.NavigateTo(NavigateUrl());
{ }
language = await LanguageService.AddLanguageAsync(language); catch (Exception ex)
{
if (language.IsDefault) await logger.LogError(ex, "Error Adding Language {Language} {Error}", language, ex.Message);
{ AddModuleMessage(Localizer["Error.Language.Add"], MessageType.Error);
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 else
{ {
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning); AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
} }
} }
private async Task InstallTranslations()
private void HideModal()
{
_productname = "";
_license = "";
StateHasChanged();
}
private async Task GetPackage(string packageid, string version)
{
try
{
var package = await PackageService.GetPackageAsync(packageid, version);
if (package != null)
{
_productname = package.Name;
if (!string.IsNullOrEmpty(package.License))
{
_license = package.License.Replace("\n", "<br />");
}
_packageid = package.PackageId;
_version = package.Version;
}
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Getting Package {PackageId} {Version}", packageid, version);
AddModuleMessage(Localizer["Error.Module.Download"], MessageType.Error);
}
}
private async Task DownloadPackage()
{
try
{
await PackageService.DownloadPackageAsync(_packageid, _version, Constants.PackagesFolder);
await logger.LogInformation("Language Package {Name} {Version} Downloaded Successfully", _packageid, _version);
AddModuleMessage(Localizer["Success.Language.Download"], MessageType.Success);
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Downloading Translation {Name} {Version}", _packageid, _version);
AddModuleMessage(Localizer["Error.Language.Download"], MessageType.Error);
}
}
private async Task InstallLanguages()
{ {
try try
{ {

View File

@ -19,94 +19,198 @@ else
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
<th>@SharedLocalizer["Name"]</th> <th>@SharedLocalizer["Name"]</th>
<th>@Localizer["Code"]</th> <th>@Localizer["Code"]</th>
<th>@Localizer["Translation"]</th>
<th>@Localizer["Default"]</th> <th>@Localizer["Default"]</th>
<th style="width: 1px;">&nbsp;</th> @if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
<th style="width: 1px;">@Localizer["Translation"]</th>
<th style="width: 1px;">&nbsp;</th>
}
</Header> </Header>
<Row> <Row>
<td><ActionDialog Header="Delete Language" Message="@string.Format(Localizer["Confirm.Language.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteLanguage(context))" Disabled="@((context.IsDefault && _languages.Count > 2) || context.Code == Constants.DefaultCulture)" ResourceKey="DeleteLanguage" /></td> <td><ActionDialog Header="Delete Language" Message="@string.Format(Localizer["Confirm.Language.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteLanguage(context))" Disabled="@((context.IsDefault && _languages.Count > 2) || context.Code == Constants.DefaultCulture)" ResourceKey="DeleteLanguage" /></td>
<td>@context.Name</td> <td>@context.Name</td>
<td>@context.Code</td> <td>@context.Code</td>
<td>@context.Version</td>
<td><TriStateCheckBox Value="@(context.IsDefault)" Disabled="true"></TriStateCheckBox></td> <td><TriStateCheckBox Value="@(context.IsDefault)" Disabled="true"></TriStateCheckBox></td>
<td> @if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
@if (UpgradeAvailable(context.Code, context.Version)) {
{ <td>@((string.IsNullOrEmpty(context.Version)) ? "---" : context.Version)</td>
<button type="button" class="btn btn-success" @onclick=@(async () => await DownloadLanguage(context.Code))>@SharedLocalizer["Upgrade"]</button> <td>
} @switch (TranslationAvailable(context.Code, context.Version))
</td> {
case "install":
<button type="button" class="btn btn-success" @onclick=@(async () => await GetPackage(context.Code))>@SharedLocalizer["Download"]</button>
break;
case "upgrade":
<button type="button" class="btn btn-success" @onclick=@(async () => await GetPackage(context.Code))>@SharedLocalizer["Upgrade"]</button>
break;
}
</td>
}
</Row> </Row>
</Pager> </Pager>
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host) && _install)
{
<button type="button" class="btn btn-success" @onclick="InstallTranslations">@SharedLocalizer["Install"]</button>
}
}
@if (_package != null)
{
<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;">
<h4 style="display: inline;"><a href="@_package.ProductUrl" target="_new">@_package.Name</a></h4><br />
@SharedLocalizer["Search.By"]:&nbsp;&nbsp;<strong><a href="@_package.OwnerUrl" target="new">@_package.Owner</a></strong><br />
@(_package.Description.Length > 400 ? (_package.Description.Substring(0, 400) + "...") : _package.Description)<br />
<strong>@(String.Format("{0:n0}", _package.Downloads))</strong> @SharedLocalizer["Search.Downloads"]&nbsp;&nbsp;|&nbsp;&nbsp;
@SharedLocalizer["Search.Released"]: <strong>@_package.ReleaseDate.ToString("MMM dd, yyyy")</strong>&nbsp;&nbsp;|&nbsp;&nbsp;
@SharedLocalizer["Search.Version"]: <strong>@_package.Version</strong>
@((MarkupString)(!string.IsNullOrEmpty(_package.PackageUrl) ? "&nbsp;&nbsp;|&nbsp;&nbsp;" + SharedLocalizer["Search.Source"] + ": <strong>" + new Uri(_package.PackageUrl).Host + "</strong>" : ""))
<br /><br />
@if (!string.IsNullOrEmpty(_package.License))
{
@((MarkupString)_package.License.Replace("\n", "<br />"))
}
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 List<Language> _languages; private List<Language> _languages;
private List<Package> _packages; private List<Package> _packages;
private Package _package;
private bool _install = false;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
protected override async Task OnParametersSetAsync() protected override async Task OnParametersSetAsync()
{
await GetLanguages();
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
_packages = await PackageService.GetPackagesAsync("translation");
}
}
private async Task GetLanguages()
{ {
_languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId, Constants.ClientId); _languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId, Constants.ClientId);
}
var cultures = await LocalizationService.GetCulturesAsync(); private async Task DeleteLanguage(Language language)
var culture = cultures.First(c => c.Name.Equals(Constants.DefaultCulture)); {
try
{
await LanguageService.DeleteLanguageAsync(language.LanguageId);
await logger.LogInformation("Language Deleted {Language}", language);
await GetLanguages();
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Deleting Language {Language} {Error}", language, ex.Message);
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) AddModuleMessage(Localizer["Error.Language.Delete"], MessageType.Error);
{ }
_packages = await PackageService.GetPackagesAsync("translation"); }
}
}
private async Task DeleteLanguage(Language language) private string TranslationAvailable(string code, string version)
{ {
try if (_packages != null)
{ {
await LanguageService.DeleteLanguageAsync(language.LanguageId);
await logger.LogInformation("Language Deleted {Language}", language);
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Deleting Language {Language} {Error}", language, ex.Message);
AddModuleMessage(Localizer["Error.Language.Delete"], MessageType.Error);
}
}
private bool UpgradeAvailable(string code, string version)
{
var upgradeavailable = false;
if (_packages != null && version != null)
{
var package = _packages.Where(item => item.PackageId == (Constants.PackageId + "." + code)).FirstOrDefault(); var package = _packages.Where(item => item.PackageId == (Constants.PackageId + "." + code)).FirstOrDefault();
if (package != null) if (package != null)
{ {
upgradeavailable = (Version.Parse(package.Version).CompareTo(Version.Parse(Constants.Version)) == 0) && // package version needs to match current framework version
(Version.Parse(package.Version).CompareTo(Version.Parse(version)) > 0); if (Version.Parse(package.Version).CompareTo(Version.Parse(Constants.Version)) == 0)
} {
if (string.IsNullOrEmpty(version))
{
return "install";
}
else
{
if (Version.Parse(package.Version).CompareTo(Version.Parse(version)) > 0)
{
return "upgrade";
}
}
}
}
}
return "";
}
} private async Task GetPackage(string code)
return upgradeavailable; {
} try
{
_package = await PackageService.GetPackageAsync(Constants.PackageId + "." + code, Constants.Version);
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Getting Package {PackageId} {Version}", Constants.PackageId + "." + code, Constants.Version);
AddModuleMessage(Localizer["Error.Translation.Download"], MessageType.Error);
}
}
private async Task DownloadLanguage(string code) private async Task DownloadPackage()
{ {
try try
{ {
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) await PackageService.DownloadPackageAsync(_package.PackageId, _package.Version, Constants.PackagesFolder);
{ await logger.LogInformation("Language Package {Name} {Version} Downloaded Successfully", _package.PackageId, _package.Version);
await PackageService.DownloadPackageAsync(Constants.PackageId + "." + code, Constants.Version, Constants.PackagesFolder); AddModuleMessage(Localizer["Success.Language.Download"], MessageType.Success);
await logger.LogInformation("Translation Downloaded {Code} {Version}", code, Constants.Version); _package = null;
await PackageService.InstallPackagesAsync(); _install = true;
AddModuleMessage(string.Format(Localizer["Success.Language.Install"], NavigateUrl("admin/system")), MessageType.Success); StateHasChanged();
} }
} catch (Exception ex)
catch (Exception ex) {
{ await logger.LogError(ex, "Error Downloading Translation {Name} {Version}", _package.PackageId, _package.Version);
await logger.LogError(ex, "Error Downloading Translation {Code} {Version} {Error}", code, Constants.Version, ex.Message); AddModuleMessage(Localizer["Error.Language.Download"], MessageType.Error);
AddModuleMessage(Localizer["Error.Language.Download"], MessageType.Error); }
} }
}
private void HideModal()
{
_package = null;
StateHasChanged();
}
private async Task InstallTranslations()
{
try
{
await PackageService.InstallPackagesAsync();
AddModuleMessage(string.Format(Localizer["Success.Translation.Install"], NavigateUrl("admin/system")), MessageType.Success);
_install = false;
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Installing Translations");
}
}
} }

View File

@ -131,7 +131,7 @@ else
moduleDefinition = await ModuleDefinitionService.CreateModuleDefinitionAsync(moduleDefinition); moduleDefinition = await ModuleDefinitionService.CreateModuleDefinitionAsync(moduleDefinition);
var settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId); var settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId);
SettingService.SetSetting(settings, "ModuleDefinitionName", moduleDefinition.ModuleDefinitionName); settings = SettingService.SetSetting(settings, "ModuleDefinitionName", moduleDefinition.ModuleDefinitionName);
await SettingService.UpdateModuleSettingsAsync(settings, ModuleState.ModuleId); await SettingService.UpdateModuleSettingsAsync(settings, ModuleState.ModuleId);
GetLocation(); GetLocation();

View File

@ -108,42 +108,50 @@
<Header> <Header>
<th>@SharedLocalizer["Name"]</th> <th>@SharedLocalizer["Name"]</th>
<th>@Localizer["Code"]</th> <th>@Localizer["Code"]</th>
<th>@Localizer["Version"]</th> <th style="width: 1px;">@Localizer["Version"]</th>
<th style="width: 1px;">&nbsp;</th> <th style="width: 1px;">&nbsp;</th>
</Header> </Header>
<Row> <Row>
<td>@context.Name</td> <td>@context.Name</td>
<td>@context.Code</td> <td>@context.Code</td>
<td>@context.Version</td> <td>@((string.IsNullOrEmpty(context.Version)) ? "---" : context.Version)</td>
<td> <td>
@if (context.IsDefault) @switch (TranslationAvailable(_packagename + "." + context.Code, context.Version))
{ {
<button type="button" class="btn btn-primary" @onclick=@(async () => await GetPackage(_packagename + "." + context.Code))>@SharedLocalizer["Download"]</button> case "install":
} <button type="button" class="btn btn-success" @onclick=@(async () => await GetPackage(_packagename + "." + context.Code))>@SharedLocalizer["Download"]</button>
else break;
{ case "upgrade":
if (UpgradeAvailable(_packagename + "." + context.Code, context.Version)) <button type="button" class="btn btn-success" @onclick=@(async () => await GetPackage(_packagename + "." + context.Code))>@SharedLocalizer["Upgrade"]</button>
{ break;
<button type="button" class="btn btn-primary" @onclick=@(async () => await DownloadPackage(_packagename + "." + context.Code))>@SharedLocalizer["Upgrade"]</button>
}
} }
</td> </td>
</Row> </Row>
</Pager> </Pager>
<button type="button" class="btn btn-success" @onclick="InstallTranslations">@SharedLocalizer["Install"]</button> @if (_install)
{
<button type="button" class="btn btn-success" @onclick="InstallTranslations">@SharedLocalizer["Install"]</button>
}
} }
else else
{ {
<br /> <br />
<div class="mx-auto text-center"> <div class="mx-auto text-center">
@Localizer["Search.NoResults"] @if (string.IsNullOrEmpty(_packagename))
{
@Localizer["Search.PackageNameMissing"]
}
else
{
@Localizer["Search.NoResults"]
}
</div> </div>
<br /> <br />
} }
</TabPanel> </TabPanel>
</TabStrip> </TabStrip>
@if (_productname != "") @if (_package != null)
{ {
<div class="app-actiondialog"> <div class="app-actiondialog">
<div class="modal" tabindex="-1" role="dialog"> <div class="modal" tabindex="-1" role="dialog">
@ -155,10 +163,17 @@
</div> </div>
<div class="modal-body"> <div class="modal-body">
<p style="height: 200px; overflow-y: scroll;"> <p style="height: 200px; overflow-y: scroll;">
<h3>@_productname</h3> <h4 style="display: inline;"><a href="@_package.ProductUrl" target="_new">@_package.Name</a></h4><br />
@if (!string.IsNullOrEmpty(_packagelicense)) @SharedLocalizer["Search.By"]:&nbsp;&nbsp;<strong><a href="@_package.OwnerUrl" target="new">@_package.Owner</a></strong><br />
@(_package.Description.Length > 400 ? (_package.Description.Substring(0, 400) + "...") : _package.Description)<br />
<strong>@(String.Format("{0:n0}", _package.Downloads))</strong> @SharedLocalizer["Search.Downloads"]&nbsp;&nbsp;|&nbsp;&nbsp;
@SharedLocalizer["Search.Released"]: <strong>@_package.ReleaseDate.ToString("MMM dd, yyyy")</strong>&nbsp;&nbsp;|&nbsp;&nbsp;
@SharedLocalizer["Search.Version"]: <strong>@_package.Version</strong>
@((MarkupString)(!string.IsNullOrEmpty(_package.PackageUrl) ? "&nbsp;&nbsp;|&nbsp;&nbsp;" + SharedLocalizer["Search.Source"] + ": <strong>" + new Uri(_package.PackageUrl).Host + "</strong>" : ""))
<br /><br />
@if (!string.IsNullOrEmpty(_package.License))
{ {
@((MarkupString)_packagelicense) @((MarkupString)_package.License.Replace("\n", "<br />"))
} }
else else
{ {
@ -167,7 +182,7 @@
</p> </p>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-success" @onclick=@(async () => await DownloadPackage(_packageid))>@SharedLocalizer["Accept"]</button> <button type="button" class="btn btn-success" @onclick="DownloadPackage">@SharedLocalizer["Accept"]</button>
<button type="button" class="btn btn-secondary" @onclick="HideModal">@SharedLocalizer["Cancel"]</button> <button type="button" class="btn btn-secondary" @onclick="HideModal">@SharedLocalizer["Cancel"]</button>
</div> </div>
</div> </div>
@ -203,9 +218,8 @@
private List<Package> _packages; private List<Package> _packages;
private List<Language> _languages; private List<Language> _languages;
private string _productname = ""; private Package _package;
private string _packagelicense = ""; private bool _install = false;
private string _packageid = "";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
@ -297,24 +311,31 @@
private void HideModal() private void HideModal()
{ {
_productname = ""; _package = null;
_packagelicense = "";
StateHasChanged(); StateHasChanged();
} }
private bool UpgradeAvailable(string packagename, string version) private string TranslationAvailable(string packagename, string version)
{ {
var upgradeavailable = false;
if (_packages != null) if (_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)
{ {
upgradeavailable = (Version.Parse(package.Version).CompareTo(Version.Parse(version)) > 0); if (string.IsNullOrEmpty(version))
{
return "install";
}
else
{
if (Version.Parse(package.Version).CompareTo(Version.Parse(version)) > 0)
{
return "upgrade";
}
}
} }
} }
return upgradeavailable; return "";
} }
private async Task GetPackage(string packagename) private async Task GetPackage(string packagename)
@ -322,16 +343,7 @@
var version = _packages.Where(item => item.PackageId == packagename).FirstOrDefault().Version; var version = _packages.Where(item => item.PackageId == packagename).FirstOrDefault().Version;
try try
{ {
var package = await PackageService.GetPackageAsync(packagename, version); _package = await PackageService.GetPackageAsync(packagename, version);
if (package != null)
{
_productname = package.Name;
if (!string.IsNullOrEmpty(package.License))
{
_packagelicense = package.License.Replace("\n", "<br />");
}
_packageid = package.PackageId;
}
StateHasChanged(); StateHasChanged();
} }
catch (Exception ex) catch (Exception ex)
@ -341,16 +353,15 @@
} }
} }
private async Task DownloadPackage(string packagename) private async Task DownloadPackage()
{ {
try try
{ {
var version = _packages.Where(item => item.PackageId == packagename).FirstOrDefault().Version; await PackageService.DownloadPackageAsync(_package.PackageId, _package.Version, Constants.PackagesFolder);
await PackageService.DownloadPackageAsync(packagename, version, Constants.PackagesFolder); await logger.LogInformation("Package {PackageId} {Version} Downloaded Successfully", _package.PackageId, _package.Version);
await logger.LogInformation("Package {PackageId} {Version} Downloaded Successfully", packagename, version);
AddModuleMessage(Localizer["Success.Translation.Download"], MessageType.Success); AddModuleMessage(Localizer["Success.Translation.Download"], MessageType.Success);
_productname = ""; _package = null;
_packagelicense = ""; _install = true;
StateHasChanged(); StateHasChanged();
} }
catch (Exception ex) catch (Exception ex)
@ -366,6 +377,8 @@
{ {
await PackageService.InstallPackagesAsync(); await PackageService.InstallPackagesAsync();
AddModuleMessage(string.Format(Localizer["Success.Translation.Install"], NavigateUrl("admin/system")), MessageType.Success); AddModuleMessage(string.Format(Localizer["Success.Translation.Install"], NavigateUrl("admin/system")), MessageType.Success);
_install = false;
StateHasChanged();
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@ -157,7 +157,8 @@
</TabPanel> </TabPanel>
} }
</TabStrip> </TabStrip>
<button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button> <br />
<button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button> <button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
</form> </form>

View File

@ -148,7 +148,8 @@
</div> </div>
</div> </div>
</Section> </Section>
<br /><br /> <br />
<br />
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon" DeletedBy="@_deletedby" DeletedOn="@_deletedon"></AuditInfo> <AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon" DeletedBy="@_deletedby" DeletedOn="@_deletedon"></AuditInfo>
} }
</TabPanel> </TabPanel>
@ -189,7 +190,8 @@
<br /> <br />
} }
</TabStrip> </TabStrip>
<button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button> <br />
<button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button> <button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
</form> </form>

View File

@ -104,7 +104,7 @@
private string modifiedby; private string modifiedby;
private DateTime modifiedon; private DateTime modifiedon;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
public override string Actions => "Add,Edit"; public override string Actions => "Add,Edit";

View File

@ -10,7 +10,7 @@
} }
else else
{ {
<ActionLink Action="Add" Security="SecurityAccessLevel.Admin" Text="Add Profile" ResourceKey="AddProfile" /> <ActionLink Action="Add" Text="Add Profile" Security="SecurityAccessLevel.Edit" ResourceKey="AddProfile" />
<Pager Items="@_profiles"> <Pager Items="@_profiles">
<Header> <Header>
@ -19,8 +19,8 @@ else
<th>@SharedLocalizer["Name"]</th> <th>@SharedLocalizer["Name"]</th>
</Header> </Header>
<Row> <Row>
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.ProfileId.ToString())" ResourceKey="EditProfile" /></td> <td><ActionLink Action="Edit" Parameters="@($"id=" + context.ProfileId.ToString())" Security="SecurityAccessLevel.Edit" ResourceKey="EditProfile" /></td>
<td><ActionDialog Header="Delete Profile" Message="@string.Format(Localizer["Confirm.Profile.Delete"], context.Name)" Action="Delete" Class="btn btn-danger" OnClick="@(async () => await DeleteProfile(context.ProfileId))" ResourceKey="DeleteProfile" /></td> <td><ActionDialog Header="Delete Profile" Message="@string.Format(Localizer["Confirm.Profile.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await DeleteProfile(context.ProfileId))" ResourceKey="DeleteProfile" /></td>
<td>@context.Name</td> <td>@context.Name</td>
</Row> </Row>
</Pager> </Pager>
@ -29,7 +29,7 @@ else
@code { @code {
private List<Profile> _profiles; private List<Profile> _profiles;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
protected override async Task OnParametersSetAsync() protected override async Task OnParametersSetAsync()
{ {

View File

@ -0,0 +1,20 @@
using Oqtane.Documentation;
using Oqtane.Models;
using Oqtane.Shared;
namespace Oqtane.Modules.Admin.Profiles
{
[PrivateApi("Mark this as private, since it's not very useful in the public docs")]
public class ModuleInfo : IModule
{
public ModuleDefinition ModuleDefinition => new ModuleDefinition
{
Name = "Profiles",
Description = "Manage Profiles",
Categories = "Admin",
Version = Constants.Version,
PermissionNames = $"{PermissionNames.View},{PermissionNames.Edit}," +
$"{EntityNames.Profile}:{PermissionNames.Write}:{RoleNames.Admin}"
};
}
}

View File

@ -42,7 +42,7 @@
private string _description = string.Empty; private string _description = string.Empty;
private string _isautoassigned = "False"; private string _isautoassigned = "False";
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
private async Task SaveRole() private async Task SaveRole()
{ {

View File

@ -49,7 +49,7 @@
private string _modifiedby; private string _modifiedby;
private DateTime _modifiedon; private DateTime _modifiedon;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {

View File

@ -10,7 +10,7 @@
} }
else else
{ {
<ActionLink Action="Add" Text="Add Role" ResourceKey="AddRole" /> <ActionLink Action="Add" Text="Add Role" Security="SecurityAccessLevel.Edit" ResourceKey="AddRole" />
<Pager Items="@_roles"> <Pager Items="@_roles">
<Header> <Header>
@ -20,9 +20,9 @@ else
<th>@SharedLocalizer["Name"]</th> <th>@SharedLocalizer["Name"]</th>
</Header> </Header>
<Row> <Row>
<td><ActionLink Action="Edit" Parameters="@($"id=" + context.RoleId.ToString())" Disabled="@(context.IsSystem)" ResourceKey="Edit" /></td> <td><ActionLink Action="Edit" Parameters="@($"id=" + context.RoleId.ToString())" Security="SecurityAccessLevel.Edit" Disabled="@(context.IsSystem)" ResourceKey="Edit" /></td>
<td><ActionDialog Header="Delete Role" Message="@string.Format(Localizer["Confirm.DeleteUser"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteRole(context))" Disabled="@(context.IsSystem)" ResourceKey="DeleteRole" /></td> <td><ActionDialog Header="Delete Role" Message="@string.Format(Localizer["Confirm.DeleteUser"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await DeleteRole(context))" Disabled="@(context.IsSystem)" ResourceKey="DeleteRole" /></td>
<td><ActionLink Action="Users" Parameters="@($"id=" + context.RoleId.ToString())" ResourceKey="Users" /></td> <td><ActionLink Action="Users" Parameters="@($"id=" + context.RoleId.ToString())" Security="SecurityAccessLevel.Edit" ResourceKey="Users" /></td>
<td>@context.Name</td> <td>@context.Name</td>
</Row> </Row>
</Pager> </Pager>
@ -31,7 +31,7 @@ else
@code { @code {
private List<Role> _roles; private List<Role> _roles;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
protected override async Task OnParametersSetAsync() protected override async Task OnParametersSetAsync()
{ {

View File

@ -0,0 +1,21 @@
using Oqtane.Documentation;
using Oqtane.Models;
using Oqtane.Shared;
namespace Oqtane.Modules.Admin.Roles
{
[PrivateApi("Mark this as private, since it's not very useful in the public docs")]
public class ModuleInfo : IModule
{
public ModuleDefinition ModuleDefinition => new ModuleDefinition
{
Name = "Roles",
Description = "Manage Roles",
Categories = "Admin",
Version = Constants.Version,
PermissionNames = $"{PermissionNames.View},{PermissionNames.Edit}," +
$"{EntityNames.Role}:{PermissionNames.Write}:{RoleNames.Admin}," +
$"{EntityNames.UserRole}:{PermissionNames.Write}:{RoleNames.Admin}"
};
}
}

View File

@ -23,13 +23,7 @@ else
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="user" HelpText="Select a user" ResourceKey="User">User: </Label> <Label Class="col-sm-3" For="user" HelpText="Select a user" ResourceKey="User">User: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
<select id="user" class="form-select" @bind="@userid" required> <AutoComplete OnSearch="GetUsers" Placeholder="@Localizer["User.Select"]" @ref="user" />
<option value="-1">&lt;@Localizer["User.Select"]&gt;</option>
@foreach (UserRole userrole in users)
{
<option value="@(userrole.UserId)">@userrole.User.DisplayName</option>
}
</select>
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
@ -64,7 +58,7 @@ else
<td>@context.EffectiveDate</td> <td>@context.EffectiveDate</td>
<td>@context.ExpiryDate</td> <td>@context.ExpiryDate</td>
<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" /> <ActionDialog Header="Remove User" Message="@string.Format(Localizer["Confirm.User.DeleteRole"], context.User.DisplayName)" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await DeleteUserRole(context.UserRoleId))" Disabled="@(context.Role.IsAutoAssigned || context.User.Username == UserNames.Host || context.User.UserId == PageState.User.UserId)" ResourceKey="DeleteUserRole" />
</td> </td>
</Row> </Row>
</Pager> </Pager>
@ -75,81 +69,96 @@ else
} }
@code { @code {
private ElementReference form; private ElementReference form;
private bool validated = false; 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 AutoComplete user;
private int userid = -1; private DateTime? effectivedate = null;
private DateTime? effectivedate = null; private DateTime? expirydate = null;
private DateTime? expirydate = null; private List<UserRole> userroles;
private List<UserRole> userroles;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
try try
{ {
roleid = Int32.Parse(PageState.QueryString["id"]); roleid = Int32.Parse(PageState.QueryString["id"]);
Role role = await RoleService.GetRoleAsync(roleid); Role role = await RoleService.GetRoleAsync(roleid);
name = role.Name; name = role.Name;
users = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Registered); await GetUserRoles();
await GetUserRoles(); }
} catch (Exception ex)
catch (Exception ex) {
{ await logger.LogError(ex, "Error Loading Users {Error}", ex.Message);
await logger.LogError(ex, "Error Loading Users {Error}", ex.Message); AddModuleMessage(Localizer["Error.User.Load"], MessageType.Error);
AddModuleMessage(Localizer["Error.User.Load"], MessageType.Error); }
} }
}
private async Task GetUserRoles() private async Task<Dictionary<string, string>> GetUsers(string filter)
{ {
try try
{ {
userroles = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, name); var users = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Registered);
} return users.Where(item => item.User.DisplayName.Contains(filter, StringComparison.OrdinalIgnoreCase))
catch (Exception ex) .ToDictionary(item => item.UserId.ToString(), item => item.User.DisplayName);
{ }
await logger.LogError(ex, "Error Loading User Roles {RoleId} {Error}", roleid, ex.Message); catch (Exception ex)
AddModuleMessage(Localizer["Error.User.LoadRole"], MessageType.Error); {
} await logger.LogError(ex, "Error Loading Users {filter} {Error}", filter, ex.Message);
} AddModuleMessage(Localizer["Error.User.Load"], MessageType.Error);
}
return new Dictionary<string, string>();
}
private async Task SaveUserRole() private async Task GetUserRoles()
{ {
validated = true; try
var interop = new Interop(JSRuntime); {
if (await interop.FormValid(form)) userroles = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, name);
{ }
try catch (Exception ex)
{ {
if (userid != -1) await logger.LogError(ex, "Error Loading User Roles {RoleId} {Error}", roleid, ex.Message);
{ AddModuleMessage(Localizer["Error.User.LoadRole"], MessageType.Error);
var userrole = userroles.Where(item => item.UserId == userid && item.RoleId == roleid).FirstOrDefault(); }
if (userrole != null) }
{
userrole.EffectiveDate = effectivedate;
userrole.ExpiryDate = expirydate;
await UserRoleService.UpdateUserRoleAsync(userrole);
}
else
{
userrole = new UserRole();
userrole.UserId = userid;
userrole.RoleId = roleid;
userrole.EffectiveDate = effectivedate;
userrole.ExpiryDate = expirydate;
await UserRoleService.AddUserRoleAsync(userrole); private async Task SaveUserRole()
} {
validated = true;
var interop = new Interop(JSRuntime);
if (await interop.FormValid(form))
{
try
{
if (!string.IsNullOrEmpty(user.Key) && int.TryParse(user.Key, out int userid))
{
var userrole = userroles.Where(item => item.UserId == userid && item.RoleId == roleid).FirstOrDefault();
if (userrole != null)
{
userrole.EffectiveDate = effectivedate;
userrole.ExpiryDate = expirydate;
await UserRoleService.UpdateUserRoleAsync(userrole);
}
else
{
userrole = new UserRole();
userrole.UserId = userid;
userrole.RoleId = roleid;
userrole.EffectiveDate = effectivedate;
userrole.ExpiryDate = expirydate;
await logger.LogInformation("User Assigned To Role {UserRole}", userrole); await UserRoleService.AddUserRoleAsync(userrole);
AddModuleMessage(Localizer["Success.User.AssignedRole"], MessageType.Success); }
await GetUserRoles();
StateHasChanged(); await logger.LogInformation("User Assigned To Role {UserRole}", userrole);
AddModuleMessage(Localizer["Success.User.AssignedRole"], MessageType.Success);
await GetUserRoles();
user.Clear();
StateHasChanged();
} }
else else
{ {

View File

@ -127,7 +127,7 @@
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="username" HelpText="Enter the username for your SMTP account" ResourceKey="SmptUsername">Username: </Label> <Label Class="col-sm-3" For="username" HelpText="Enter the username for your SMTP account" ResourceKey="SmtpUsername">Username: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
<input id="username" class="form-control" @bind="@_smtpusername" /> <input id="username" class="form-control" @bind="@_smtpusername" />
</div> </div>
@ -142,12 +142,21 @@
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="sender" HelpText="Enter the email which emails will be sent from. Please note that this email address may need to be authorized with the SMTP server." ResourceKey="SmptSender">Email Sender: </Label> <Label 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="SmtpSender">Email Sender: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
<input id="sender" class="form-control" @bind="@_smtpsender" /> <input id="sender" class="form-control" @bind="@_smtpsender" />
</div> </div>
</div> </div>
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="relay" HelpText="Only specify this option if you have properly configured an SMTP Relay Service to route your outgoing mail. This option will send notifications from the user's email rather than from the Email Sender specified above." ResourceKey="SmtpRelay">Relay Configured? </Label>
<div class="col-sm-9">
<select id="relay" class="form-select" @bind="@_smtprelay" 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="retention" HelpText="Number of days of notifications to retain" ResourceKey="Retention">Retention (Days): </Label> <Label Class="col-sm-3" For="retention" HelpText="Number of days of notifications to retain" ResourceKey="Retention">Retention (Days): </Label>
<div class="col-sm-9"> <div class="col-sm-9">
<input id="retention" class="form-control" @bind="@_retention" /> <input id="retention" class="form-control" @bind="@_retention" />
@ -320,6 +329,7 @@
private string _smtppasswordtype = "password"; private string _smtppasswordtype = "password";
private string _togglesmtppassword = string.Empty; private string _togglesmtppassword = string.Empty;
private string _smtpsender = string.Empty; private string _smtpsender = string.Empty;
private string _smtprelay = "False";
private string _retention = string.Empty; private string _retention = string.Empty;
private string _pwaisenabled; private string _pwaisenabled;
private int _pwaappiconfileid = -1; private int _pwaappiconfileid = -1;
@ -393,6 +403,7 @@
_smtppassword = SettingService.GetSetting(settings, "SMTPPassword", string.Empty); _smtppassword = SettingService.GetSetting(settings, "SMTPPassword", string.Empty);
_togglesmtppassword = SharedLocalizer["ShowPassword"]; _togglesmtppassword = SharedLocalizer["ShowPassword"];
_smtpsender = SettingService.GetSetting(settings, "SMTPSender", string.Empty); _smtpsender = SettingService.GetSetting(settings, "SMTPSender", string.Empty);
_smtprelay = SettingService.GetSetting(settings, "SMTPRelay", "False");
_retention = SettingService.GetSetting(settings, "NotificationRetention", "30"); _retention = SettingService.GetSetting(settings, "NotificationRetention", "30");
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
@ -444,7 +455,7 @@
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Loading Pane Layouts For Theme {ThemeType} {Error}", _themetype, ex.Message); await logger.LogError(ex, "Error Loading Containers For Theme {ThemeType} {Error}", _themetype, ex.Message);
AddModuleMessage(Localizer["Error.Theme.LoadPane"], MessageType.Error); AddModuleMessage(Localizer["Error.Theme.LoadPane"], MessageType.Error);
} }
} }
@ -532,6 +543,7 @@
settings = SettingService.SetSetting(settings, "SMTPUsername", _smtpusername, true); settings = SettingService.SetSetting(settings, "SMTPUsername", _smtpusername, true);
settings = SettingService.SetSetting(settings, "SMTPPassword", _smtppassword, true); settings = SettingService.SetSetting(settings, "SMTPPassword", _smtppassword, true);
settings = SettingService.SetSetting(settings, "SMTPSender", _smtpsender, true); settings = SettingService.SetSetting(settings, "SMTPSender", _smtpsender, true);
settings = SettingService.SetSetting(settings, "SMTPRelay", _smtprelay, true);
settings = SettingService.SetSetting(settings, "NotificationRetention", _retention, true); settings = SettingService.SetSetting(settings, "NotificationRetention", _retention, true);
await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId); await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId);
@ -602,12 +614,12 @@
try try
{ {
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId); var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
SettingService.SetSetting(settings, "SMTPHost", _smtphost, true); settings = SettingService.SetSetting(settings, "SMTPHost", _smtphost, true);
SettingService.SetSetting(settings, "SMTPPort", _smtpport, true); settings = SettingService.SetSetting(settings, "SMTPPort", _smtpport, true);
SettingService.SetSetting(settings, "SMTPSSL", _smtpssl, true); settings = SettingService.SetSetting(settings, "SMTPSSL", _smtpssl, true);
SettingService.SetSetting(settings, "SMTPUsername", _smtpusername, true); settings = SettingService.SetSetting(settings, "SMTPUsername", _smtpusername, true);
SettingService.SetSetting(settings, "SMTPPassword", _smtppassword, true); settings = SettingService.SetSetting(settings, "SMTPPassword", _smtppassword, true);
SettingService.SetSetting(settings, "SMTPSender", _smtpsender, true); settings = SettingService.SetSetting(settings, "SMTPSender", _smtpsender, true);
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId); await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
await logger.LogInformation("Site SMTP Settings Saved"); await logger.LogInformation("Site SMTP Settings Saved");

View File

@ -16,7 +16,7 @@ else
<div class="container"> <div class="container">
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="tenant" HelpText="Select the tenant for the SQL server" ResourceKey="Tenant">Tenant: </Label> <Label Class="col-sm-3" For="tenant" HelpText="Select the tenant associated with the database server" ResourceKey="Tenant">Tenant: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
<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>
@ -38,13 +38,16 @@ else
<div class="row mb-1 align-items-center"> <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> <Label Class="col-sm-3" For="connectionstring" HelpText="The connection information for the database" ResourceKey="ConnectionString">Connection: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
<textarea id="connectionstring" class="form-control" @bind="@_connectionstring" rows="2" readonly></textarea> <div class="input-group">
</div> <input id="connectionstring" type="@_connectionstringtype" class="form-control" @bind="@_connectionstring" readonly />
</div> <button type="button" class="btn btn-secondary" @onclick="@ToggleConnectionString">@_connectionstringtoggle</button>
</div>
</div>
</div>
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="sqlQeury" HelpText="Enter the query for the SQL server" ResourceKey="SqlQuery">SQL Query: </Label> <Label Class="col-sm-3" For="sqlQuery" HelpText="Enter the SQL query for the database server" ResourceKey="SqlQuery">SQL Query: </Label>
<div class="col-sm-9"> <div class="col-sm-9">
<textarea id="sqlQeury" class="form-control" @bind="@_sql" rows="3"></textarea> <textarea id="sqlQuery" class="form-control" @bind="@_sql" rows="3"></textarea>
</div> </div>
</div> </div>
} }
@ -85,8 +88,10 @@ else
private List<Tenant> _tenants; private List<Tenant> _tenants;
private string _tenantid = "-1"; private string _tenantid = "-1";
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 _connectionstringtype = "password";
private string _connectionstringtoggle = string.Empty;
private string _sql = string.Empty;
private List<Dictionary<string, string>> _results; private List<Dictionary<string, string>> _results;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
@ -96,7 +101,8 @@ else
try try
{ {
_tenants = await TenantService.GetTenantsAsync(); _tenants = await TenantService.GetTenantsAsync();
} _connectionstringtoggle = SharedLocalizer["ShowPassword"];
}
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Loading Tenants {Error}", ex.Message); await logger.LogError(ex, "Error Loading Tenants {Error}", ex.Message);
@ -126,7 +132,21 @@ else
} }
} }
private async Task Execute() private void ToggleConnectionString()
{
if (_connectionstringtype == "password")
{
_connectionstringtype = "text";
_connectionstringtoggle = SharedLocalizer["HidePassword"];
}
else
{
_connectionstringtype = "password";
_connectionstringtoggle = SharedLocalizer["ShowPassword"];
}
}
private async Task Execute()
{ {
try try
{ {

View File

@ -147,6 +147,16 @@
<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;
<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="Log" Heading="Log" ResourceKey="Log">
<div class="container">
<div class="row mb-1 align-items-center">
<Label Class="col-sm-3" For="log" HelpText="System log information for current day" ResourceKey="Log">Log: </Label>
<div class="col-sm-9">
<textarea id="log" class="form-control" rows="10" @bind="@_log" readonly />
</div>
</div>
</div>
</TabPanel>
</TabStrip> </TabStrip>
<br /><br /> <br /><br />
@ -172,6 +182,8 @@
private string _swagger = string.Empty; private string _swagger = string.Empty;
private string _packageservice = string.Empty; private string _packageservice = string.Empty;
private string _log = string.Empty;
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
_version = Constants.Version; _version = Constants.Version;
@ -191,19 +203,25 @@
_workingset = (Convert.ToInt64(systeminfo["WorkingSet"].ToString()) / 1000000).ToString() + " MB"; _workingset = (Convert.ToInt64(systeminfo["WorkingSet"].ToString()) / 1000000).ToString() + " MB";
} }
systeminfo = await SystemService.GetSystemInfoAsync(); systeminfo = await SystemService.GetSystemInfoAsync("configuration");
if (systeminfo != null) if (systeminfo != null)
{ {
_installationid = systeminfo["InstallationId"].ToString(); _installationid = systeminfo["InstallationId"].ToString();
_detailederrors = systeminfo["DetailedErrors"].ToString(); _detailederrors = systeminfo["DetailedErrors"].ToString();
_logginglevel = systeminfo["Logging:LogLevel:Default"].ToString(); _logginglevel = systeminfo["Logging:LogLevel:Default"].ToString();
_notificationlevel = systeminfo["Logging:LogLevel:Notify"].ToString(); _notificationlevel = systeminfo["Logging:LogLevel:Notify"].ToString();
_swagger = systeminfo["UseSwagger"].ToString(); _swagger = systeminfo["UseSwagger"].ToString();
_packageservice = systeminfo["PackageService"].ToString(); _packageservice = systeminfo["PackageService"].ToString();
}
systeminfo = await SystemService.GetSystemInfoAsync("log");
if (systeminfo != null)
{
_log = systeminfo["Log"].ToString();
} }
} }
private async Task SaveConfig() private async Task SaveConfig()
{ {
try try
{ {

View File

@ -224,126 +224,140 @@ else
<br /><br /> <br /><br />
@code { @code {
private string username = string.Empty; private string username = string.Empty;
private string _password = string.Empty; private string _password = string.Empty;
private string _passwordtype = "password"; private string _passwordtype = "password";
private string _togglepassword = string.Empty; private string _togglepassword = string.Empty;
private string confirm = string.Empty; private string confirm = string.Empty;
private bool allowtwofactor = false; private bool allowtwofactor = false;
private string twofactor = "False"; private string twofactor = "False";
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 folderid = -1;
private int photofileid = -1; private int photofileid = -1;
private File photo = null; private File photo = null;
private List<Profile> profiles; private List<Profile> profiles;
private Dictionary<string, string> settings; private Dictionary<string, string> settings;
private string category = string.Empty; private string category = string.Empty;
private string filter = "to"; private string filter = "to";
private List<Notification> notifications; private List<Notification> notifications;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
protected override async Task OnParametersSetAsync() protected override async Task OnParametersSetAsync()
{ {
try try
{ {
_togglepassword = SharedLocalizer["ShowPassword"]; _togglepassword = SharedLocalizer["ShowPassword"];
if (PageState.Site.Settings.ContainsKey("LoginOptions:TwoFactor") && !string.IsNullOrEmpty(PageState.Site.Settings["LoginOptions:TwoFactor"])) if (PageState.Site.Settings.ContainsKey("LoginOptions:TwoFactor") && !string.IsNullOrEmpty(PageState.Site.Settings["LoginOptions:TwoFactor"]))
{ {
allowtwofactor = (PageState.Site.Settings["LoginOptions:TwoFactor"] == "true"); allowtwofactor = (PageState.Site.Settings["LoginOptions:TwoFactor"] == "true");
} }
if (PageState.User != null) if (PageState.User != null)
{ {
username = PageState.User.Username; username = PageState.User.Username;
twofactor = PageState.User.TwoFactorRequired.ToString(); twofactor = PageState.User.TwoFactorRequired.ToString();
email = PageState.User.Email; email = PageState.User.Email;
displayname = PageState.User.DisplayName; displayname = PageState.User.DisplayName;
// get user folder // get user folder
var folder = await FolderService.GetFolderAsync(ModuleState.SiteId, PageState.User.FolderPath); var folder = await FolderService.GetFolderAsync(ModuleState.SiteId, PageState.User.FolderPath);
if (folder != null) if (folder != null)
{ {
folderid = folder.FolderId; folderid = folder.FolderId;
} }
if (PageState.User.PhotoFileId != null) if (PageState.User.PhotoFileId != null)
{ {
photofileid = PageState.User.PhotoFileId.Value; photofileid = PageState.User.PhotoFileId.Value;
photo = await FileService.GetFileAsync(photofileid); photo = await FileService.GetFileAsync(photofileid);
} }
else else
{ {
photofileid = -1; photofileid = -1;
photo = null; photo = null;
} }
profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId); profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId);
settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId); settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId);
await LoadNotificationsAsync(); await LoadNotificationsAsync();
} }
else else
{ {
AddModuleMessage(Localizer["Message.User.NoLogIn"], MessageType.Warning); AddModuleMessage(Localizer["Message.User.NoLogIn"], MessageType.Warning);
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Loading User Profile {Error}", ex.Message); await logger.LogError(ex, "Error Loading User Profile {Error}", ex.Message);
AddModuleMessage(Localizer["Error.Profile.Load"], MessageType.Error); AddModuleMessage(Localizer["Error.Profile.Load"], MessageType.Error);
} }
} }
private async Task LoadNotificationsAsync() private async Task LoadNotificationsAsync()
{ {
notifications = await NotificationService.GetNotificationsAsync(PageState.Site.SiteId, filter, PageState.User.UserId); notifications = await NotificationService.GetNotificationsAsync(PageState.Site.SiteId, filter, PageState.User.UserId);
notifications = notifications.Where(item => item.DeletedBy != PageState.User.Username).ToList(); notifications = notifications.Where(item => item.DeletedBy != PageState.User.Username).ToList();
} }
private string GetProfileValue(string SettingName, string DefaultValue) private string GetProfileValue(string SettingName, string DefaultValue)
=> SettingService.GetSetting(settings, SettingName, DefaultValue); {
string value = SettingService.GetSetting(settings, SettingName, DefaultValue);
if (value.Contains("]"))
{
value = value.Substring(value.IndexOf("]") + 1);
}
return value;
}
private async Task Save() private async Task Save()
{ {
try try
{ {
if (username != string.Empty && email != string.Empty && ValidateProfiles()) if (username != string.Empty && email != string.Empty && ValidateProfiles())
{ {
if (_password == confirm) if (_password == confirm)
{ {
var user = PageState.User; var user = PageState.User;
user.Username = username; user.Username = username;
user.Password = _password; user.Password = _password;
user.TwoFactorRequired = bool.Parse(twofactor); user.TwoFactorRequired = bool.Parse(twofactor);
user.Email = email; user.Email = email;
user.DisplayName = (displayname == string.Empty ? username : displayname); user.DisplayName = (displayname == string.Empty ? username : displayname);
user.PhotoFileId = filemanager.GetFileId(); user.PhotoFileId = filemanager.GetFileId();
if (user.PhotoFileId == -1) if (user.PhotoFileId == -1)
{ {
user.PhotoFileId = null; user.PhotoFileId = null;
} }
if (user.PhotoFileId != null) if (user.PhotoFileId != null)
{ {
photofileid = user.PhotoFileId.Value; photofileid = user.PhotoFileId.Value;
photo = await FileService.GetFileAsync(photofileid); photo = await FileService.GetFileAsync(photofileid);
} }
else else
{ {
photofileid = -1; photofileid = -1;
photo = null; photo = null;
} }
await UserService.UpdateUserAsync(user); user = await UserService.UpdateUserAsync(user);
await SettingService.UpdateUserSettingsAsync(settings, PageState.User.UserId); if (user != null)
await logger.LogInformation("User Profile Saved"); {
await SettingService.UpdateUserSettingsAsync(settings, PageState.User.UserId);
await logger.LogInformation("User Profile Saved");
AddModuleMessage(Localizer["Success.Profile.Update"], MessageType.Success); AddModuleMessage(Localizer["Success.Profile.Update"], MessageType.Success);
StateHasChanged(); StateHasChanged();
} }
else
{
AddModuleMessage(Localizer["Message.Password.Complexity"], MessageType.Error);
}
}
else else
{ {
AddModuleMessage(Localizer["Message.Password.Invalid"], MessageType.Warning); AddModuleMessage(Localizer["Message.Password.Invalid"], MessageType.Warning);

View File

@ -104,7 +104,7 @@
private Dictionary<string, string> settings; private Dictionary<string, string> settings;
private string category = string.Empty; private string category = string.Empty;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
@ -121,8 +121,15 @@
} }
} }
private string GetProfileValue(string SettingName, string DefaultValue) private string GetProfileValue(string SettingName, string DefaultValue)
=> SettingService.GetSetting(settings, SettingName, DefaultValue); {
string value = SettingService.GetSetting(settings, SettingName, DefaultValue);
if (value.Contains("]"))
{
value = value.Substring(value.IndexOf("]") + 1);
}
return value;
}
private async Task SaveUser() private async Task SaveUser()
{ {

View File

@ -174,7 +174,7 @@ else
private string deletedby; private string deletedby;
private DateTime? deletedon; private DateTime? deletedon;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
protected override async Task OnParametersSetAsync() protected override async Task OnParametersSetAsync()
{ {
@ -201,58 +201,71 @@ else
photofileid = -1; photofileid = -1;
photo = null; photo = null;
} }
isdeleted = user.IsDeleted.ToString(); isdeleted = user.IsDeleted.ToString();
lastlogin = string.Format("{0:MMM dd yyyy HH:mm:ss}", user.LastLoginOn); lastlogin = string.Format("{0:MMM dd yyyy HH:mm:ss}", user.LastLoginOn);
lastipaddress = user.LastIPAddress; lastipaddress = user.LastIPAddress;
settings = await SettingService.GetUserSettingsAsync(user.UserId); settings = await SettingService.GetUserSettingsAsync(user.UserId);
createdby = user.CreatedBy; createdby = user.CreatedBy;
createdon = user.CreatedOn; createdon = user.CreatedOn;
modifiedby = user.ModifiedBy; modifiedby = user.ModifiedBy;
modifiedon = user.ModifiedOn; modifiedon = user.ModifiedOn;
deletedby = user.DeletedBy; deletedby = user.DeletedBy;
deletedon = user.DeletedOn; deletedon = user.DeletedOn;
} }
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "Error Loading User {UserId} {Error}", userid, ex.Message); await logger.LogError(ex, "Error Loading User {UserId} {Error}", userid, ex.Message);
AddModuleMessage(Localizer["Error.User.Load"], MessageType.Error); AddModuleMessage(Localizer["Error.User.Load"], MessageType.Error);
} }
} }
private string GetProfileValue(string SettingName, string DefaultValue) private string GetProfileValue(string SettingName, string DefaultValue)
=> SettingService.GetSetting(settings, SettingName, DefaultValue); {
string value = SettingService.GetSetting(settings, SettingName, DefaultValue);
if (value.Contains("]"))
{
value = value.Substring(value.IndexOf("]") + 1);
}
return value;
}
private async Task SaveUser() private async Task SaveUser()
{ {
try try
{ {
if (username != string.Empty && email != string.Empty && ValidateProfiles()) if (username != string.Empty && email != string.Empty && ValidateProfiles())
{ {
if (_password == confirm) if (_password == confirm)
{ {
var user = await UserService.GetUserAsync(userid, PageState.Site.SiteId); var user = await UserService.GetUserAsync(userid, PageState.Site.SiteId);
user.SiteId = PageState.Site.SiteId; user.SiteId = PageState.Site.SiteId;
user.Username = username; user.Username = username;
user.Password = _password; user.Password = _password;
user.Email = email; user.Email = email;
user.DisplayName = string.IsNullOrWhiteSpace(displayname) ? username : displayname; user.DisplayName = string.IsNullOrWhiteSpace(displayname) ? username : displayname;
user.PhotoFileId = null; user.PhotoFileId = null;
user.PhotoFileId = filemanager.GetFileId(); user.PhotoFileId = filemanager.GetFileId();
if (user.PhotoFileId == -1) if (user.PhotoFileId == -1)
{ {
user.PhotoFileId = null; user.PhotoFileId = null;
} }
user.IsDeleted = (isdeleted == null ? true : Boolean.Parse(isdeleted)); user.IsDeleted = (isdeleted == null ? true : Boolean.Parse(isdeleted));
user = await UserService.UpdateUserAsync(user); user = await UserService.UpdateUserAsync(user);
await SettingService.UpdateUserSettingsAsync(settings, user.UserId); if (user != null)
await logger.LogInformation("User Saved {User}", user); {
await SettingService.UpdateUserSettingsAsync(settings, user.UserId);
NavigationManager.NavigateTo(NavigateUrl()); await logger.LogInformation("User Saved {User}", user);
NavigationManager.NavigateTo(NavigateUrl());
}
else
{
AddModuleMessage(Localizer["Message.Password.Complexity"], MessageType.Error);
}
} }
else else
{ {

View File

@ -20,7 +20,7 @@ else
<div class="container"> <div class="container">
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
<div class="col-sm-4"> <div class="col-sm-4">
<ActionLink Action="Add" Text="Add User" ResourceKey="AddUser" /> <ActionLink Action="Add" Text="Add User" Security="SecurityAccessLevel.Edit" ResourceKey="AddUser" />
</div> </div>
<div class="col-sm-4"> <div class="col-sm-4">
<input class="form-control" @bind="@_search" /> <input class="form-control" @bind="@_search" />
@ -41,21 +41,21 @@ else
</Header> </Header>
<Row> <Row>
<td> <td>
<ActionLink Action="Edit" Parameters="@($"id=" + context.UserId.ToString())" ResourceKey="EditUser" /> <ActionLink Action="Edit" Parameters="@($"id=" + context.UserId.ToString())" Security="SecurityAccessLevel.Edit" 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.UserId == PageState.User.UserId)" ResourceKey="DeleteUser" /> <ActionDialog Header="Delete User" Message="@string.Format(Localizer["Confirm.User.Delete"], context.User.DisplayName)" Action="Delete" Security="SecurityAccessLevel.Edit" 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())" Security="SecurityAccessLevel.Edit" ResourceKey="Roles" />
</td> </td>
<td>@context.User.Username</td> <td>@context.User.Username</td>
<td>@((MarkupString)string.Format("<a href=\"mailto:{0}\">{1}</a>", @context.User.Email, @context.User.DisplayName))</td> <td>@((MarkupString)string.Format("<a href=\"mailto:{0}\">{1}</a>", @context.User.Email, @context.User.DisplayName))</td>
<td>@string.Format("{0:dd-MMM-yyyy HH:mm:ss}", context.User.LastLoginOn)</td> <td>@((context.User.LastLoginOn != DateTime.MinValue) ? string.Format("{0:dd-MMM-yyyy HH:mm:ss}", context.User.LastLoginOn) : "")</td>
</Row> </Row>
</Pager> </Pager>
</TabPanel> </TabPanel>
<TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings"> <TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings" Security="SecurityAccessLevel.Admin">
<div class="container"> <div class="container">
<Section Name="User" Heading="User Settings" ResourceKey="UserSettings"> <Section Name="User" Heading="User Settings" ResourceKey="UserSettings">
<div class="row mb-1 align-items-center"> <div class="row mb-1 align-items-center">
@ -406,7 +406,7 @@ else
private string _lifetime; private string _lifetime;
private string _token; private string _token;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
@ -456,7 +456,8 @@ else
_togglesecret = SharedLocalizer["ShowPassword"]; _togglesecret = SharedLocalizer["ShowPassword"];
_issuer = SettingService.GetSetting(settings, "JwtOptions:Issuer", PageState.Uri.Scheme + "://" + PageState.Alias.Name); _issuer = SettingService.GetSetting(settings, "JwtOptions:Issuer", PageState.Uri.Scheme + "://" + PageState.Alias.Name);
_audience = SettingService.GetSetting(settings, "JwtOptions:Audience", ""); _audience = SettingService.GetSetting(settings, "JwtOptions:Audience", "");
_lifetime = SettingService.GetSetting(settings, "JwtOptions:Lifetime", "20"); } _lifetime = SettingService.GetSetting(settings, "JwtOptions:Lifetime", "20");
}
} }
private async Task LoadUsersAsync(bool load) private async Task LoadUsersAsync(bool load)
@ -522,7 +523,7 @@ else
private async Task UpdateUserSettingsAsync() private async Task UpdateUserSettingsAsync()
{ {
Dictionary<string, string> settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId); Dictionary<string, string> settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId);
SettingService.SetSetting(settings, settingSearch, _search); settings = SettingService.SetSetting(settings, settingSearch, _search);
await SettingService.UpdateUserSettingsAsync(settings, PageState.User.UserId); await SettingService.UpdateUserSettingsAsync(settings, PageState.User.UserId);
} }

View File

@ -0,0 +1,21 @@
using Oqtane.Documentation;
using Oqtane.Models;
using Oqtane.Shared;
namespace Oqtane.Modules.Admin.Users
{
[PrivateApi("Mark this as private, since it's not very useful in the public docs")]
public class ModuleInfo : IModule
{
public ModuleDefinition ModuleDefinition => new ModuleDefinition
{
Name = "Users",
Description = "Manage Users",
Categories = "Admin",
Version = Constants.Version,
PermissionNames = $"{PermissionNames.View},{PermissionNames.Edit}," +
$"{EntityNames.User}:{PermissionNames.Write}:{RoleNames.Admin}," +
$"{EntityNames.UserRole}:{PermissionNames.Write}:{RoleNames.Admin}"
};
}
}

View File

@ -63,7 +63,7 @@ else
<td>@context.EffectiveDate</td> <td>@context.EffectiveDate</td>
<td>@context.ExpiryDate</td> <td>@context.ExpiryDate</td>
<td> <td>
<ActionDialog Header="Remove Role" Message="@string.Format(Localizer["Confirm.User.RemoveRole"], context.Role.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteUserRole(context.UserRoleId))" Disabled="@(context.Role.IsAutoAssigned || (context.Role.Name == RoleNames.Host && userid == PageState.User.UserId))" ResourceKey="DeleteUserRole" /> <ActionDialog Header="Remove Role" Message="@string.Format(Localizer["Confirm.User.RemoveRole"], context.Role.Name)" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await DeleteUserRole(context.UserRoleId))" Disabled="@(context.Role.IsAutoAssigned || (context.Role.Name == RoleNames.Host && userid == PageState.User.UserId))" ResourceKey="DeleteUserRole" />
</td> </td>
</Row> </Row>
</Pager> </Pager>
@ -79,7 +79,7 @@ else
private string expirydate = string.Empty; private string expirydate = string.Empty;
private List<UserRole> userroles; private List<UserRole> userroles;
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin; public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {

View File

@ -0,0 +1,153 @@
@namespace Oqtane.Modules.Controls
@inherits LocalizableComponent
<div class="app-autocomplete">
<input class="form-control" value="@Value" @oninput="OnInput" @onkeyup="OnKeyUp" placeholder="@Placeholder" autocomplete="off" />
@if (_results != null)
{
<select class="form-select" style="position: relative;" value="@Value" size="@Rows" @onkeyup="OnKeyUp" @onchange="(e => OnChange(e))">
@if (_results.Any())
{
@foreach (var result in _results)
{
if (result.Value == Value)
{
<option selected>@result.Value</option>
}
else
{
<option>@result.Value</option>
}
}
}
else
{
<option disabled>No Results</option>
}
</select>
}
</div>
@code {
Dictionary<string, string> _results;
[Parameter]
public Func<string, Task<Dictionary<string, string>>> OnSearch { get; set; } // required - an async delegate method which accepts a filter string parameter and returns a dictionary
[Parameter]
public int Characters { get; set; } = 3; // optional - number of characters before search is initiated
[Parameter]
public int Rows { get; set; } = 3; // optional - number of result rows to display
[Parameter]
public string Placeholder { get; set; } // optional - placeholder input text
[Parameter]
public string Value { get; set; } // value of item selected
[Parameter]
public string Key { get; set; } // key of item selected
private async Task OnInput(ChangeEventArgs e)
{
Value = e.Value?.ToString();
if (Value?.Length >= Characters)
{
_results = await OnSearch?.Invoke(Value);
}
else
{
_results = null;
}
SetKey();
}
private async Task OnKeyUp(KeyboardEventArgs e)
{
var index = -1;
switch (e.Key)
{
case "ArrowDown":
if (_results == null)
{
if (Value?.Length >= Characters)
{
_results = await OnSearch?.Invoke(Value);
}
}
else
{
index = GetIndex();
if (index < _results.Count - 1)
{
Value = _results.ElementAt(index + 1).Value;
Key = _results.ElementAt(index + 1).Key;
}
}
break;
case "ArrowUp":
index = GetIndex();
if (index > 0)
{
Value = _results.ElementAt(index - 1).Value;
Key = _results.ElementAt(index - 1).Key;
}
break;
case "ArrowRight":
case "Tab":
_results = null;
break;
case "Enter": // note within a form the enter key submits the entire form
case "NumpadEnter":
_results = null;
break;
case "Escape":
Value = "";
_results = null;
break;
}
}
private void OnChange(ChangeEventArgs e)
{
Value = (string)e.Value;
SetKey();
_results = null;
}
private int GetIndex()
{
if (_results != null)
{
for (int index = 0; index < _results.Count; index++)
{
if (_results.ElementAt(index).Value == Value)
{
return index;
}
}
}
return -1;
}
private void SetKey()
{
var index = GetIndex();
if (index != -1)
{
Key = _results.ElementAt(index).Key;
}
else
{
Key = "";
}
}
public void Clear()
{
Value = "";
Key = "";
_results = null;
}
}

View File

@ -1,4 +1,5 @@
@namespace Oqtane.Modules.Controls @namespace Oqtane.Modules.Controls
@using System.Threading
@inherits ModuleControlBase @inherits ModuleControlBase
@inject IFolderService FolderService @inject IFolderService FolderService
@inject IFileService FileService @inject IFileService FileService
@ -53,7 +54,7 @@
} }
</div> </div>
<div class="col mt-2 text-end"> <div class="col mt-2 text-end">
<button type="button" class="btn btn-success" @onclick="UploadFile">@SharedLocalizer["Upload"]</button> <button type="button" class="btn btn-success" @onclick="UploadFiles">@SharedLocalizer["Upload"]</button>
@if (ShowFiles && GetFileId() != -1) @if (ShowFiles && GetFileId() != -1)
{ {
<button type="button" class="btn btn-danger mx-1" @onclick="DeleteFile">@SharedLocalizer["Delete"]</button> <button type="button" class="btn btn-danger mx-1" @onclick="DeleteFile">@SharedLocalizer["Delete"]</button>
@ -86,7 +87,6 @@
} }
@code { @code {
private string _id;
private List<Folder> _folders; private List<Folder> _folders;
private List<File> _files = new List<File>(); private List<File> _files = new List<File>();
private string _fileinputid = string.Empty; private string _fileinputid = string.Empty;
@ -144,11 +144,6 @@
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
if (!string.IsNullOrEmpty(Id))
{
_id = Id;
}
// packages folder is a framework folder for uploading installable nuget packages // packages folder is a framework folder for uploading installable nuget packages
if (Folder == Constants.PackagesFolder) if (Folder == Constants.PackagesFolder)
{ {
@ -207,9 +202,9 @@
// create unique id for component // create unique id for component
_guid = Guid.NewGuid().ToString("N"); _guid = Guid.NewGuid().ToString("N");
_fileinputid = _guid + "FileInput"; _fileinputid = "FileInput_" + _guid;
_progressinfoid = _guid + "ProgressInfo"; _progressinfoid = "ProgressInfo_" + _guid;
_progressbarid = _guid + "ProgressBar"; _progressbarid = "ProgressBar_" + _guid;
} }
private async Task GetFiles() private async Task GetFiles()
@ -304,17 +299,17 @@
} }
} }
private async Task UploadFile() private async Task UploadFiles()
{ {
_message = string.Empty; _message = string.Empty;
var interop = new Interop(JSRuntime); var interop = new Interop(JSRuntime);
var upload = await interop.GetFiles(_fileinputid); var uploads = await interop.GetFiles(_fileinputid);
if (upload.Length > 0) if (uploads.Length > 0)
{ {
string restricted = ""; string restricted = "";
foreach (var file in upload) foreach (var upload in uploads)
{ {
var extension = (file.LastIndexOf(".") != -1) ? file.Substring(file.LastIndexOf(".") + 1) : ""; var extension = (upload.LastIndexOf(".") != -1) ? upload.Substring(upload.LastIndexOf(".") + 1) : "";
if (!Constants.UploadableFiles.Split(',').Contains(extension.ToLower())) if (!Constants.UploadableFiles.Split(',').Contains(extension.ToLower()))
{ {
restricted += (restricted == "" ? "" : ",") + extension; restricted += (restricted == "" ? "" : ",") + extension;
@ -324,48 +319,68 @@
{ {
try try
{ {
string result; // upload the files
if (Folder == Constants.PackagesFolder) var posturl = Utilities.TenantUrl(PageState.Alias, "/api/file/upload");
var folder = (Folder == Constants.PackagesFolder) ? Folder : FolderId.ToString();
await interop.UploadFiles(posturl, folder, _guid, SiteState.AntiForgeryToken);
// uploading is asynchronous so we need to wait for the uploads to complete
// note that this will only wait a maximum of 15 seconds which may not be long enough for very large file uploads
bool success = false;
int attempts = 0;
while (attempts < 5 && !success)
{ {
result = await FileService.UploadFilesAsync(Folder, upload, _guid); attempts += 1;
} Thread.Sleep(1000 * attempts); // progressive retry
else
{ success = true;
result = await FileService.UploadFilesAsync(FolderId, upload, _guid); List<File> files = await FileService.GetFilesAsync(folder);
if (files.Count > 0)
{
foreach (string upload in uploads)
{
if (!files.Exists(item => item.Name == upload))
{
success = false;
}
}
}
} }
if (result == string.Empty) // reset progress indicators
await interop.SetElementAttribute(_guid + "ProgressInfo", "style", "display: none;");
await interop.SetElementAttribute(_guid + "ProgressBar", "style", "display: none;");
if (success)
{ {
await logger.LogInformation("File Upload Succeeded {Files}", upload); await logger.LogInformation("File Upload Succeeded {Files}", uploads);
if (ShowSuccess) if (ShowSuccess)
{ {
_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();
var file = _files.Where(item => item.Name == upload[0]).FirstOrDefault();
if (file != null)
{
FileId = file.FileId;
await SetImage();
await OnUpload.InvokeAsync(FileId);
}
StateHasChanged();
} }
else else
{ {
await logger.LogError("File Upload Failed For {Files}", result.Replace(",", ", ")); await logger.LogInformation("File Upload Failed Or Is Still In Progress {Files}", uploads);
_message = Localizer["Error.File.Upload"]; _message = Localizer["Error.File.Upload"];
_messagetype = MessageType.Error; _messagetype = MessageType.Error;
} }
// set FileId to first file in upload collection
await GetFiles();
var file = _files.Where(item => item.Name == uploads[0]).FirstOrDefault();
if (file != null)
{
FileId = file.FileId;
await SetImage();
await OnUpload.InvokeAsync(FileId);
}
StateHasChanged();
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.LogError(ex, "File Upload Failed {Error}", ex.Message); await logger.LogError(ex, "File Upload Failed {Error}", ex.Message);
_message = Localizer["Error.File.Upload"]; _message = Localizer["Error.File.Upload"];
_messagetype = MessageType.Error; _messagetype = MessageType.Error;
} }

View File

@ -8,16 +8,16 @@
@if ((Toolbar == "Top" || Toolbar == "Both") && _pages > 0 && Items.Count() > _maxItems) @if ((Toolbar == "Top" || Toolbar == "Both") && _pages > 0 && Items.Count() > _maxItems)
{ {
<ul class="pagination justify-content-center my-2"> <ul class="pagination justify-content-center my-2">
<li class="page-item@((_page > 1) ? "" : " disabled")"> <li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => UpdateList(1))><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a> <a class="page-link" @onclick=@(async () => UpdateList(1))><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
</li> </li>
@if (_pages > _displayPages && _displayPages > 1) @if (_pages > _displayPages && _displayPages > 1)
{ {
<li class="page-item@((_page > _displayPages) ? "" : " disabled")"> <li class="page-item@((_page > _displayPages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => SkipPages("back"))><span class="oi oi-media-skip-backward" title="skip back" aria-hidden="true"></span></a> <a class="page-link" @onclick=@(async () => SkipPages("back"))><span class="oi oi-media-skip-backward" title="skip back" aria-hidden="true"></span></a>
</li> </li>
} }
<li class="page-item@((_page > 1) ? "" : " disabled")"> <li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => NavigateToPage("previous"))><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></a> <a class="page-link" @onclick=@(async () => NavigateToPage("previous"))><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></a>
</li> </li>
@for (int i = _startPage; i <= _endPage; i++) @for (int i = _startPage; i <= _endPage; i++)
@ -25,27 +25,27 @@
var pager = i; var pager = i;
if (pager == _page) if (pager == _page)
{ {
<li class="page-item active"> <li class="page-item app-pager-pointer active">
<a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a> <a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a>
</li> </li>
} }
else else
{ {
<li class="page-item"> <li class="page-item app-pager-pointer">
<a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a> <a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a>
</li> </li>
} }
} }
<li class="page-item@((_page < _pages) ? "" : " disabled")"> <li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => NavigateToPage("next"))><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></a> <a class="page-link" @onclick=@(async () => NavigateToPage("next"))><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></a>
</li> </li>
@if (_pages > _displayPages && _displayPages > 1) @if (_pages > _displayPages && _displayPages > 1)
{ {
<li class="page-item@((_endPage < _pages) ? "" : " disabled")"> <li class="page-item@((_endPage < _pages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => SkipPages("forward"))><span class="oi oi-media-skip-forward" title="skip forward" aria-hidden="true"></span></a> <a class="page-link" @onclick=@(async () => SkipPages("forward"))><span class="oi oi-media-skip-forward" title="skip forward" aria-hidden="true"></span></a>
</li> </li>
} }
<li class="page-item@((_page < _pages) ? "" : " disabled")"> <li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => UpdateList(_pages))><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a> <a class="page-link" @onclick=@(async () => UpdateList(_pages))><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a>
</li> </li>
<li class="page-item disabled"> <li class="page-item disabled">
@ -55,46 +55,46 @@
} }
@if (Format == "Table" && Row != null) @if (Format == "Table" && Row != null)
{ {
<div class="table-responsive"> <div class="table-responsive">
<table class="@Class"> <table class="@Class">
<thead> <thead>
<tr class="@RowClass">@Header</tr> <tr class="@RowClass">@Header</tr>
</thead> </thead>
<tbody> <tbody>
@foreach (var item in ItemList) @foreach (var item in ItemList)
{ {
<tr class="@RowClass">@Row(item)</tr> <tr class="@RowClass">@Row(item)</tr>
@if (Detail != null) @if (Detail != null)
{ {
<tr>@Detail(item)</tr> <tr>@Detail(item)</tr>
} }
} }
</tbody> </tbody>
<tfoot> <tfoot>
<tr class="@RowClass">@Footer</tr> <tr class="@RowClass">@Footer</tr>
</tfoot> </tfoot>
</table> </table>
</div> </div>
} }
@if (Format == "Grid" && Row != null) @if (Format == "Grid" && Row != null)
{ {
int count = 0; int count = 0;
int rows = 0; int rows = 0;
int cols = 0; int cols = 0;
if (ItemList != null) if (ItemList != null)
{ {
if (_columns == 0) if (_columns == 0)
{ {
count = ItemList.Count(); count = ItemList.Count();
rows = 1; rows = 1;
cols = count; cols = count;
} }
else else
{ {
count = (int)Math.Ceiling(ItemList.Count() / (decimal)_columns) * _columns; count = (int)Math.Ceiling(ItemList.Count() / (decimal)_columns) * _columns;
rows = count / _columns; rows = count / _columns;
cols = _columns; cols = _columns;
} }
} }
<div class="@Class"> <div class="@Class">
@for (int row = 0; row < rows; row++) @for (int row = 0; row < rows; row++)
@ -116,19 +116,19 @@
} }
</div> </div>
} }
@if ((Toolbar == "Bottom" || Toolbar == "Both") && _pages > 0 && Items.Count() > _maxItems) @if ((Toolbar == "Bottom" || Toolbar == "Both") && _pages > 0 && Items.Count() > _maxItems)
{ {
<ul class="pagination justify-content-center my-2"> <ul class="pagination justify-content-center my-2">
<li class="page-item@((_page > 1) ? "" : " disabled")"> <li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => UpdateList(1))><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a> <a class="page-link" @onclick=@(async () => UpdateList(1))><span class="oi oi-media-step-backward" title="start" aria-hidden="true"></span></a>
</li> </li>
@if (_pages > _displayPages && _displayPages > 1) @if (_pages > _displayPages && _displayPages > 1)
{ {
<li class="page-item@((_page > _displayPages) ? "" : " disabled")"> <li class="page-item@((_page > _displayPages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => SkipPages("back"))><span class="oi oi-media-skip-backward" title="skip back" aria-hidden="true"></span></a> <a class="page-link" @onclick=@(async () => SkipPages("back"))><span class="oi oi-media-skip-backward" title="skip back" aria-hidden="true"></span></a>
</li> </li>
} }
<li class="page-item@((_page > 1) ? "" : " disabled")"> <li class="page-item@((_page > 1) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => NavigateToPage("previous"))><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></a> <a class="page-link" @onclick=@(async () => NavigateToPage("previous"))><span class="oi oi-chevron-left" title="previous" aria-hidden="true"></span></a>
</li> </li>
@for (int i = _startPage; i <= _endPage; i++) @for (int i = _startPage; i <= _endPage; i++)
@ -136,100 +136,100 @@
var pager = i; var pager = i;
if (pager == _page) if (pager == _page)
{ {
<li class="page-item active"> <li class="page-item app-pager-pointer active">
<a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a> <a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a>
</li> </li>
} }
else else
{ {
<li class="page-item"> <li class="page-item app-pager-pointer">
<a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a> <a class="page-link" @onclick=@(async () => UpdateList(pager))>@pager</a>
</li> </li>
} }
} }
<li class="page-item@((_page < _pages) ? "" : " disabled")"> <li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => NavigateToPage("next"))><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></a> <a class="page-link" @onclick=@(async () => NavigateToPage("next"))><span class="oi oi-chevron-right" title="next" aria-hidden="true"></span></a>
</li> </li>
@if (_pages > _displayPages && _displayPages > 1) @if (_pages > _displayPages && _displayPages > 1)
{ {
<li class="page-item@((_endPage < _pages) ? "" : " disabled")"> <li class="page-item@((_endPage < _pages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => SkipPages("forward"))><span class="oi oi-media-skip-forward" title="skip forward" aria-hidden="true"></span></a> <a class="page-link" @onclick=@(async () => SkipPages("forward"))><span class="oi oi-media-skip-forward" title="skip forward" aria-hidden="true"></span></a>
</li> </li>
} }
<li class="page-item@((_page < _pages) ? "" : " disabled")"> <li class="page-item@((_page < _pages) ? " app-pager-pointer" : " disabled")">
<a class="page-link" @onclick=@(async () => UpdateList(_pages))><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a> <a class="page-link" @onclick=@(async () => UpdateList(_pages))><span class="oi oi-media-step-forward" title="end" aria-hidden="true"></span></a>
</li> </li>
<li class="page-item disabled"> <li class="page-item disabled">
<a class="page-link" style="white-space: nowrap;">@Localizer["PageOfPages", _page, _pages]</a> <a class="page-link" style="white-space: nowrap;">@Localizer["PageOfPages", _page, _pages]</a>
</li> </li>
</ul> </ul>
} }
} }
@code { @code {
private IStringLocalizer Localizer; private IStringLocalizer Localizer;
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 _displayPages = 5; private int _displayPages = 5;
private int _startPage = 0; private int _startPage = 0;
private int _endPage = 0; private int _endPage = 0;
private int _columns = 0; private int _columns = 0;
[Parameter] [Parameter]
public string Format { get; set; } // Table or Grid public string Format { get; set; } // Table or Grid
[Parameter] [Parameter]
public string Toolbar { get; set; } // Top, Bottom or Both public string Toolbar { get; set; } // Top, Bottom or Both
[Parameter] [Parameter]
public RenderFragment Header { get; set; } = null; // only applicable to Table layouts public RenderFragment Header { get; set; } = null; // only applicable to Table layouts
[Parameter] [Parameter]
public RenderFragment<TableItem> Row { get; set; } = null; // required public RenderFragment<TableItem> Row { get; set; } = null; // required
[Parameter] [Parameter]
public RenderFragment Footer { get; set; } = null; // only applicable to Table layouts public RenderFragment Footer { get; set; } = null; // only applicable to Table layouts
[Parameter] [Parameter]
public RenderFragment<TableItem> Detail { get; set; } = null; // only applicable to Table layouts public RenderFragment<TableItem> Detail { get; set; } = null; // only applicable to Table layouts
[Parameter] [Parameter]
public IEnumerable<TableItem> Items { get; set; } // the IEnumerable data source public IEnumerable<TableItem> Items { get; set; } // the IEnumerable data source
[Parameter] [Parameter]
public string PageSize { get; set; } // number of items to display on a page public string PageSize { get; set; } // number of items to display on a page
[Parameter] [Parameter]
public string Columns { get; set; } // only applicable to Grid layouts - default is zero indicating use responsive behavior public string Columns { get; set; } // only applicable to Grid layouts - default is zero indicating use responsive behavior
[Parameter] [Parameter]
public string CurrentPage { get; set; } // sets the initial page to display public string CurrentPage { get; set; } // sets the initial page to display
[Parameter] [Parameter]
public string DisplayPages { get; set; } // maximum number of page numbers to display for user selection public string DisplayPages { get; set; } // maximum number of page numbers to display for user selection
[Parameter] [Parameter]
public string Class { get; set; } // class for the containing element - ie. <table> for Table or <div> for Grid public string Class { get; set; } // class for the containing element - ie. <table> for Table or <div> for Grid
[Parameter] [Parameter]
public string RowClass { get; set; } // class for row element - ie. <tr> for Table or <div> for Grid public string RowClass { get; set; } // class for row element - ie. <tr> for Table or <div> for Grid
[Parameter] [Parameter]
public string ColumnClass { get; set; } // class for column element - only applicable to Grid format public string ColumnClass { get; set; } // class for column element - only applicable to Grid format
[Parameter] [Parameter]
public Action<int> OnPageChange { get; set; } // a method to be executed in the calling component when the page changes public Action<int> OnPageChange { get; set; } // a method to be executed in the calling component when the page changes
private IEnumerable<TableItem> ItemList { get; set; } private IEnumerable<TableItem> ItemList { get; set; }
protected override void OnInitialized() protected override void OnInitialized()
{ {
Localizer = LocalizerFactory.Create(GetType().FullName); Localizer = LocalizerFactory.Create(GetType().FullName);
} }
protected override void OnParametersSet() protected override void OnParametersSet()
{ {
if (string.IsNullOrEmpty(Format)) if (string.IsNullOrEmpty(Format))
{ {
Format = "Table"; Format = "Table";
@ -276,7 +276,7 @@
} }
} }
if (!string.IsNullOrEmpty(PageSize)) if (!string.IsNullOrEmpty(PageSize))
{ {
_maxItems = int.Parse(PageSize); _maxItems = int.Parse(PageSize);
} }
@ -299,7 +299,7 @@
{ {
_page = 1; _page = 1;
} }
if (_page < 1) _page = 1; if (_page < 1) _page = 1;
_startPage = 0; _startPage = 0;
_endPage = 0; _endPage = 0;
@ -311,7 +311,6 @@
{ {
_page = _pages; _page = _pages;
} }
ItemList = Items.Skip((_page - 1) * _maxItems).Take(_maxItems);
SetPagerSize(); SetPagerSize();
} }
} }
@ -324,13 +323,13 @@
{ {
_endPage = _pages; _endPage = _pages;
} }
OnPageChange?.Invoke(_page); ItemList = Items.Skip((_page - 1) * _maxItems).Take(_maxItems);
StateHasChanged(); StateHasChanged();
} OnPageChange?.Invoke(_page);
}
public void UpdateList(int page) public void UpdateList(int page)
{ {
ItemList = Items.Skip((page - 1) * _maxItems).Take(_maxItems);
_page = page; _page = page;
SetPagerSize(); SetPagerSize();
} }

View File

@ -2,6 +2,7 @@
@inherits ModuleControlBase @inherits ModuleControlBase
@inject IRoleService RoleService @inject IRoleService RoleService
@inject IUserService UserService @inject IUserService UserService
@inject IUserRoleService UserRoleService
@inject IStringLocalizer<PermissionGrid> Localizer @inject IStringLocalizer<PermissionGrid> Localizer
@inject IStringLocalizer<SharedResources> SharedLocalizer @inject IStringLocalizer<SharedResources> SharedLocalizer
@ -16,8 +17,8 @@
<th scope="col">@Localizer["Role"]</th> <th scope="col">@Localizer["Role"]</th>
@foreach (PermissionString permission in _permissions) @foreach (PermissionString permission in _permissions)
{ {
<th style="text-align: center; width: 1px;">@Localizer[permission.PermissionName]</th> <th style="text-align: center; width: 1px;">@((MarkupString)GetPermissionName(permission).Replace(" ", "<br />"))</th>
} }
</tr> </tr>
@foreach (Role role in _roles) @foreach (Role role in _roles)
{ {
@ -27,7 +28,7 @@
{ {
var p = permission; var p = permission;
<td style="text-align: center;"> <td style="text-align: center;">
<TriStateCheckBox Value=@GetPermissionValue(p.Permissions, role.Name) Disabled=@GetPermissionDisabled(role.Name) OnChange="@(e => PermissionChanged(e, p.PermissionName, role.Name))" /> <TriStateCheckBox Value=@GetPermissionValue(p.Permissions, role.Name) Disabled="@GetPermissionDisabled(p.EntityName, p.PermissionName, role.Name)" OnChange="@(e => PermissionChanged(e, p.EntityName, p.PermissionName, role.Name))" />
</td> </td>
} }
</tr> </tr>
@ -65,7 +66,7 @@
{ {
var p = permission; var p = permission;
<td style="text-align: center; width: 1px;"> <td style="text-align: center; width: 1px;">
<TriStateCheckBox Value=@GetPermissionValue(p.Permissions, userid) Disabled=false OnChange="@(e => PermissionChanged(e, p.PermissionName, userid))" /> <TriStateCheckBox Value=@GetPermissionValue(p.Permissions, userid) Disabled="@GetPermissionDisabled(p.EntityName, p.PermissionName, "")" OnChange="@(e => PermissionChanged(e, p.EntityName, p.PermissionName, userid))" />
</td> </td>
} }
</tr> </tr>
@ -77,23 +78,16 @@
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col"> <div class="col-11">
<table class="table table-borderless"> <AutoComplete OnSearch="GetUsers" Placeholder="@Localizer["Username.Enter"]" @ref="_user" />
<tbody> </div>
<tr> <div class="col-1">
<td class="input-group"> <button type="button" class="btn btn-primary" @onclick="AddUser">@SharedLocalizer["Add"]</button>
<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> </div>
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<ModuleMessage Type="MessageType.Error" Message="@_message" /> <ModuleMessage Type="MessageType.Warning" Message="@_message" />
</div> </div>
</div> </div>
</div> </div>
@ -104,7 +98,7 @@
private List<Role> _roles; private List<Role> _roles;
private List<PermissionString> _permissions; private List<PermissionString> _permissions;
private List<User> _users = new List<User>(); private List<User> _users = new List<User>();
private string _username = string.Empty; private AutoComplete _user;
private string _message = string.Empty; private string _message = string.Empty;
[Parameter] [Parameter]
@ -135,10 +129,25 @@
_permissions = new List<PermissionString>(); _permissions = new List<PermissionString>();
foreach (string permissionname in _permissionnames.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) foreach (string permissionname in _permissionnames.Split(',', StringSplitOptions.RemoveEmptyEntries))
{ {
// initialize with admin role // permission names can be in the form of "EntityName:PermissionName:Roles"
_permissions.Add(new PermissionString { PermissionName = permissionname, Permissions = RoleNames.Admin }); if (permissionname.Contains(":"))
{
var segments = permissionname.Split(':');
if (segments.Length == 3)
{
if (!segments[2].Contains(RoleNames.Admin))
{
segments[2] = RoleNames.Admin + ";" + segments[2]; // ensure admin access
}
_permissions.Add(new PermissionString { EntityName = segments[0], PermissionName = segments[1], Permissions = segments[2] });
}
}
else
{
_permissions.Add(new PermissionString { EntityName = EntityName, PermissionName = permissionname, Permissions = RoleNames.Admin });
}
} }
if (!string.IsNullOrEmpty(Permissions)) if (!string.IsNullOrEmpty(Permissions))
@ -146,14 +155,15 @@
// populate permissions // populate permissions
foreach (PermissionString permissionstring in UserSecurity.GetPermissionStrings(Permissions)) foreach (PermissionString permissionstring in UserSecurity.GetPermissionStrings(Permissions))
{ {
if (_permissions.Find(item => item.PermissionName == permissionstring.PermissionName) != null) int index = _permissions.FindIndex(item => item.EntityName == permissionstring.EntityName && item.PermissionName == permissionstring.PermissionName);
if (index != -1)
{ {
_permissions[_permissions.FindIndex(item => item.PermissionName == permissionstring.PermissionName)].Permissions = permissionstring.Permissions; _permissions[index].Permissions = permissionstring.Permissions;
} }
if (permissionstring.Permissions.Contains("[")) if (permissionstring.Permissions.Contains("["))
{ {
foreach (string user in permissionstring.Permissions.Split(new char[] { '[' }, StringSplitOptions.RemoveEmptyEntries)) foreach (string user in permissionstring.Permissions.Split('[', StringSplitOptions.RemoveEmptyEntries))
{ {
if (user.Contains("]")) if (user.Contains("]"))
{ {
@ -169,6 +179,16 @@
} }
} }
private string GetPermissionName(PermissionString permission)
{
var permissionname = Localizer[permission.PermissionName].ToString();
if (!string.IsNullOrEmpty(EntityName))
{
permissionname += " " + Localizer[permission.EntityName].ToString();
}
return permissionname;
}
private bool? GetPermissionValue(string permissions, string securityKey) private bool? GetPermissionValue(string permissions, string securityKey)
{ {
if ((";" + permissions + ";").Contains(";" + "!" + securityKey + ";")) if ((";" + permissions + ";").Contains(";" + "!" + securityKey + ";"))
@ -188,38 +208,58 @@
} }
} }
private bool GetPermissionDisabled(string roleName) private bool GetPermissionDisabled(string entityName, string permissionName, string roleName)
=> (roleName == RoleNames.Admin && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) ? true : false; {
if (roleName == RoleNames.Admin && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
{
return true;
}
else
{
if (entityName != EntityName && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
{
return true;
}
else
{
return false;
}
}
}
private async Task<Dictionary<string, string>> GetUsers(string filter)
{
var users = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId, RoleNames.Registered);
return users.Where(item => item.User.DisplayName.Contains(filter, StringComparison.OrdinalIgnoreCase))
.ToDictionary(item => item.UserId.ToString(), item => item.User.DisplayName);
}
private async Task AddUser() private async Task AddUser()
{ {
if (_users.Where(item => item.Username == _username).FirstOrDefault() == null) if (!string.IsNullOrEmpty(_user.Key))
{ {
try var user = await UserService.GetUserAsync(int.Parse(_user.Key), ModuleState.SiteId);
if (user != null && !_users.Any(item => item.UserId == user.UserId))
{ {
var user = await UserService.GetUserAsync(_username, ModuleState.SiteId); _users.Add(user);
if (user != null)
{
_users.Add(user);
}
}
catch
{
_message = Localizer["Message.Username.DontExist"];
} }
} }
else
_username = string.Empty; {
_message = Localizer["Message.Username.DontExist"];
}
_user.Clear();
} }
private void PermissionChanged(bool? value, string permissionName, string securityId) private void PermissionChanged(bool? value, string entityName, string permissionName, string securityId)
{ {
var selected = value; var selected = value;
var permission = _permissions.Find(item => item.PermissionName == permissionName); int index = _permissions.FindIndex(item => item.EntityName == entityName && item.PermissionName == permissionName);
if (permission != null) if (index != -1)
{ {
var ids = permission.Permissions.Split(';').ToList(); var permission = _permissions[index];
var ids = permission.Permissions.Split(';').ToList();
ids.Remove(securityId); // remove grant permission ids.Remove(securityId); // remove grant permission
ids.Remove("!" + securityId); // remove deny permission ids.Remove("!" + securityId); // remove deny permission
@ -235,7 +275,7 @@
break; // permission not specified break; // permission not specified
} }
_permissions[_permissions.FindIndex(item => item.PermissionName == permissionName)].Permissions = string.Join(";", ids.ToArray()); _permissions[index].Permissions = string.Join(";", ids.ToArray());
} }
} }
@ -248,9 +288,9 @@
private void ValidatePermissions() private void ValidatePermissions()
{ {
PermissionString permission; PermissionString permission;
for (int i = 0; i < _permissions.Count; i++) for (int index = 0; index < _permissions.Count; index++)
{ {
permission = _permissions[i]; permission = _permissions[index];
List<string> ids = permission.Permissions.Split(';', StringSplitOptions.RemoveEmptyEntries).ToList(); List<string> ids = permission.Permissions.Split(';', StringSplitOptions.RemoveEmptyEntries).ToList();
ids.Remove("!" + RoleNames.Everyone); // remove deny all users ids.Remove("!" + RoleNames.Everyone); // remove deny all users
ids.Remove("!" + RoleNames.Unauthenticated); // remove deny unauthenticated ids.Remove("!" + RoleNames.Unauthenticated); // remove deny unauthenticated
@ -266,7 +306,7 @@
} }
} }
permission.Permissions = string.Join(";", ids.ToArray()); permission.Permissions = string.Join(";", ids.ToArray());
_permissions[i] = permission; _permissions[index] = permission;
} }
} }
} }

View File

@ -3,13 +3,13 @@
@if (Name == Parent.ActiveTab) @if (Name == Parent.ActiveTab)
{ {
<div id="@Name" class="tab-pane fade show active" role="tabpanel"> <div id="@(Parent.Id + Name)" class="tab-pane fade show active" role="tabpanel">
@ChildContent @ChildContent
</div> </div>
} }
else else
{ {
<div id="@Name" class="tab-pane fade" role="tabpanel"> <div id="@(Parent.Id + Name)" class="tab-pane fade" role="tabpanel">
@ChildContent @ChildContent
</div> </div>
} }

View File

@ -10,13 +10,13 @@
<li class="nav-item" @key="tabPanel.Name"> <li class="nav-item" @key="tabPanel.Name">
@if (tabPanel.Name == ActiveTab) @if (tabPanel.Name == ActiveTab)
{ {
<a class="nav-link active" data-bs-toggle="tab" href="#@tabPanel.Name" role="tab" @onclick:preventDefault="true"> <a class="nav-link active" data-bs-toggle="tab" href="#@(Id + tabPanel.Name)" role="tab" @onclick:preventDefault="true">
@tabPanel.DisplayHeading() @tabPanel.DisplayHeading()
</a> </a>
} }
else else
{ {
<a class="nav-link" data-bs-toggle="tab" href="#@tabPanel.Name" role="tab" @onclick:preventDefault="true"> <a class="nav-link" data-bs-toggle="tab" href="#@(Id + tabPanel.Name)" role="tab" @onclick:preventDefault="true">
@tabPanel.DisplayHeading() @tabPanel.DisplayHeading()
</a> </a>
} }
@ -32,18 +32,31 @@
</CascadingValue> </CascadingValue>
@code { @code {
private List<TabPanel> _tabPanels; private List<TabPanel> _tabPanels;
private string _tabpanelid = string.Empty;
[Parameter] [Parameter]
public RenderFragment ChildContent { get; set; } // contains the TabPanels public RenderFragment ChildContent { get; set; } // contains the TabPanels
[Parameter] [Parameter]
public string ActiveTab { get; set; } // optional - defaults to first TabPanel if not specified. Can also be set using a "tab=" querystring parameter. public string ActiveTab { get; set; } // optional - defaults to first TabPanel if not specified. Can also be set using a "tab=" querystring parameter.
[Parameter] [Parameter]
public bool Refresh { get; set; } // optional - used in scenarios where TabPanels are added/removed dynamically within a parent form. ActiveTab may need to be reset as well when this property is used. public bool Refresh { get; set; } // optional - used in scenarios where TabPanels are added/removed dynamically within a parent form. ActiveTab may need to be reset as well when this property is used.
protected override void OnParametersSet() [Parameter]
public string Id { get; set; } // optional - used to uniquely identify an instance of a tab strip component (will be set automatically if no value provided)
protected override void OnInitialized()
{
if (string.IsNullOrEmpty(Id))
{
// create unique id for component
Id = "TabStrip_" + Guid.NewGuid().ToString("N") + "_" ;
}
}
protected override void OnParametersSet()
{ {
if (PageState.QueryString.ContainsKey("tab")) if (PageState.QueryString.ContainsKey("tab"))
{ {

View File

@ -72,15 +72,24 @@ namespace Oqtane.Modules
{ {
if (Resources != null && Resources.Exists(item => item.ResourceType == ResourceType.Script)) if (Resources != null && Resources.Exists(item => item.ResourceType == ResourceType.Script))
{ {
var interop = new Interop(JSRuntime);
var scripts = new List<object>(); var scripts = new List<object>();
var inline = 0;
foreach (Resource resource in Resources.Where(item => item.ResourceType == ResourceType.Script)) foreach (Resource resource in Resources.Where(item => item.ResourceType == ResourceType.Script))
{ {
var url = (resource.Url.Contains("://")) ? resource.Url : PageState.Alias.BaseUrl + resource.Url; if (!string.IsNullOrEmpty(resource.Url))
scripts.Add(new { href = url, bundle = resource.Bundle ?? "", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", es6module = resource.ES6Module }); {
var url = (resource.Url.Contains("://")) ? resource.Url : PageState.Alias.BaseUrl + resource.Url;
scripts.Add(new { href = url, bundle = resource.Bundle ?? "", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", es6module = resource.ES6Module });
}
else
{
inline += 1;
await interop.IncludeScript(GetType().Namespace.ToLower() + inline.ToString(), "", "", "", resource.Content, resource.Location.ToString().ToLower());
}
} }
if (scripts.Any()) if (scripts.Any())
{ {
var interop = new Interop(JSRuntime);
await interop.IncludeScripts(scripts.ToArray()); await interop.IncludeScripts(scripts.ToArray());
} }
} }
@ -306,15 +315,10 @@ namespace Oqtane.Modules
{ {
int pageId = ModuleState.PageId; int pageId = ModuleState.PageId;
int moduleId = ModuleState.ModuleId; int moduleId = ModuleState.ModuleId;
int? userId = null;
if (PageState.User != null)
{
userId = PageState.User.UserId;
}
string category = GetType().AssemblyQualifiedName; string category = GetType().AssemblyQualifiedName;
string feature = Utilities.GetTypeNameLastSegment(category, 1); string feature = Utilities.GetTypeNameLastSegment(category, 1);
await LoggingService.Log(alias, pageId, moduleId, userId, category, feature, function, level, exception, message, args); await LoggingService.Log(alias, pageId, moduleId, PageState.User?.UserId, category, feature, function, level, exception, message, args);
} }
public class Logger public class Logger

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>3.2.1</Version> <Version>3.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/v3.2.1</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.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>

View File

@ -16,6 +16,7 @@ using Microsoft.JSInterop;
using Oqtane.Documentation; using Oqtane.Documentation;
using Oqtane.Modules; using Oqtane.Modules;
using Oqtane.Services; using Oqtane.Services;
using Oqtane.Shared;
using Oqtane.UI; using Oqtane.UI;
namespace Oqtane.Client namespace Oqtane.Client
@ -56,7 +57,11 @@ namespace Oqtane.Client
RegisterClientStartups(assembly, builder.Services); RegisterClientStartups(assembly, builder.Services);
} }
await builder.Build().RunAsync(); var host = builder.Build();
await SetCultureFromLocalizationCookie(host.Services);
await host.RunAsync();
} }
private static async Task LoadClientAssemblies(HttpClient http, IServiceProvider serviceProvider) private static async Task LoadClientAssemblies(HttpClient http, IServiceProvider serviceProvider)
@ -220,7 +225,7 @@ namespace Oqtane.Client
var localizationCookie = await interop.GetCookie(CookieRequestCultureProvider.DefaultCookieName); var localizationCookie = await interop.GetCookie(CookieRequestCultureProvider.DefaultCookieName);
var culture = CookieRequestCultureProvider.ParseCookieValue(localizationCookie)?.UICultures?[0].Value; var culture = CookieRequestCultureProvider.ParseCookieValue(localizationCookie)?.UICultures?[0].Value;
var localizationService = serviceProvider.GetRequiredService<ILocalizationService>(); var localizationService = serviceProvider.GetRequiredService<ILocalizationService>();
var cultures = await localizationService.GetCulturesAsync(); var cultures = await localizationService.GetCulturesAsync(false);
if (culture == null || !cultures.Any(c => c.Name.Equals(culture, StringComparison.OrdinalIgnoreCase))) if (culture == null || !cultures.Any(c => c.Name.Equals(culture, StringComparison.OrdinalIgnoreCase)))
{ {

View File

@ -136,7 +136,7 @@
<value>Please Enter All Required Fields. Ensure Passwords Match And Email Address Provided Is Valid.</value> <value>Please Enter All Required Fields. Ensure Passwords Match And Email Address Provided Is Valid.</value>
</data> </data>
<data name="Message.Password.Invalid" xml:space="preserve"> <data name="Message.Password.Invalid" xml:space="preserve">
<value>The Password Provided Does Not Meet The Password Policy. Please Verify The Minimum Password Length And Complexity Requirements.</value> <value>The Password Provided Does Not Meet The Complexity Policy. Passwords Must Be At Least 6 Characters In Length And Contain Uppercase, Lowercase, Numeric, And Punctuation Characters.</value>
</data> </data>
<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>

View File

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

View File

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

View File

@ -117,45 +117,30 @@
<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="The Only Supported Culture That Has Been Defined Is English" xml:space="preserve">
<value>The Only Supported Culture That Has Been Defined Is English</value>
</data>
<data name="Error.Language.Add" xml:space="preserve"> <data name="Error.Language.Add" xml:space="preserve">
<value>Error Adding Language</value> <value>Error Adding Language</value>
</data> </data>
<data name="Translated.HelpText" xml:space="preserve">
<value>Specify If You Wish To Select Languages That Have Translations Installed</value>
</data>
<data name="Name.HelpText" xml:space="preserve"> <data name="Name.HelpText" xml:space="preserve">
<value>Name Of The Langauage</value> <value>Name Of The Langauage</value>
</data> </data>
<data name="IsDefault.HelpText" xml:space="preserve"> <data name="IsDefault.HelpText" xml:space="preserve">
<value>Indicates Whether Or Not This Language Is The Default For The Site</value> <value>Indicates Whether Or Not This Language Is The Default For The Site</value>
</data> </data>
<data name="Translated.Text" xml:space="preserve">
<value>Translated?</value>
</data>
<data name="Name.Text" xml:space="preserve"> <data name="Name.Text" xml:space="preserve">
<value>Name:</value> <value>Name:</value>
</data> </data>
<data name="IsDefault.Text" xml:space="preserve"> <data name="IsDefault.Text" xml:space="preserve">
<value>Default?</value> <value>Default?</value>
</data> </data>
<data name="AllLanguages" xml:space="preserve">
<value>All The Installed Languages Have Been Added.</value>
</data>
<data name="Error.Language.Download" xml:space="preserve">
<value>Error Downloading Translation</value>
</data>
<data name="OnlyEnglish" xml:space="preserve">
<value>The Only Installed Language Is English</value>
</data>
<data name="Success.Language.Download" xml:space="preserve">
<value>Translation Downloaded Successfully. Click Install To Complete Installation.</value>
</data>
<data name="Success.Language.Install" xml:space="preserve"> <data name="Success.Language.Install" xml:space="preserve">
<value>Translations Installed Successfully. You Must &lt;a href={0}&gt;Restart&lt;/a&gt; Your Application To Apply These Changes.</value> <value>Translations Installed Successfully. You Must &lt;a href={0}&gt;Restart&lt;/a&gt; Your Application To Apply These Changes.</value>
</data> </data>
<data name="Search.NoResults" xml:space="preserve">
<value>No Translations Match The Criteria Provided Or Package Service Is Disabled</value>
</data>
<data name="Download.Heading" xml:space="preserve">
<value>Translations</value>
</data>
<data name="LanguageUpload.HelpText" xml:space="preserve"> <data name="LanguageUpload.HelpText" xml:space="preserve">
<value>Upload one or more translation packages. Once they are uploaded click Install to complete the installation.</value> <value>Upload one or more translation packages. Once they are uploaded click Install to complete the installation.</value>
</data> </data>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<root> <root>
<!-- <!--
Microsoft ResX Schema Microsoft ResX Schema
@ -204,6 +204,9 @@
<data name="Error.Translation.Download" xml:space="preserve"> <data name="Error.Translation.Download" xml:space="preserve">
<value>Error Downloading Translation</value> <value>Error Downloading Translation</value>
</data> </data>
<data name="Search.PackageNameMissing" xml:space="preserve">
<value>A Package Name Was Not Provided For The Module</value>
</data>
<data name="Search.NoResults" xml:space="preserve"> <data name="Search.NoResults" xml:space="preserve">
<value>No Translations Exist For This Module Or Package Service Is Disabled</value> <value>No Translations Exist For This Module Or Package Service Is Disabled</value>
</data> </data>

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<root> <root>
<!-- <!--
Microsoft ResX Schema Microsoft ResX Schema
@ -121,7 +121,7 @@
<value>User: </value> <value>User: </value>
</data> </data>
<data name="User.Select" xml:space="preserve"> <data name="User.Select" xml:space="preserve">
<value>Select User</value> <value>Enter User's Name</value>
</data> </data>
<data name="Users" xml:space="preserve"> <data name="Users" xml:space="preserve">
<value>Users</value> <value>Users</value>
@ -129,9 +129,6 @@
<data name="Error.User.Load" xml:space="preserve"> <data name="Error.User.Load" xml:space="preserve">
<value>Error Loading Users</value> <value>Error Loading Users</value>
</data> </data>
<data name="Error.User.LoadRole" xml:space="preserve">
<value>Error Loading User Roles</value>
</data>
<data name="Success.User.AssignedRole" xml:space="preserve"> <data name="Success.User.AssignedRole" xml:space="preserve">
<value>User Assigned To Role</value> <value>User Assigned To Role</value>
</data> </data>
@ -151,7 +148,7 @@
<value>The role you are assigning users to</value> <value>The role you are assigning users to</value>
</data> </data>
<data name="User.HelpText" xml:space="preserve"> <data name="User.HelpText" xml:space="preserve">
<value>Select a user</value> <value>Enter the name of a user</value>
</data> </data>
<data name="EffectiveDate.HelpText" xml:space="preserve"> <data name="EffectiveDate.HelpText" xml:space="preserve">
<value>The date that this role assignment is active</value> <value>The date that this role assignment is active</value>

View File

@ -195,13 +195,13 @@
<data name="UseSsl.HelpText" xml:space="preserve"> <data name="UseSsl.HelpText" xml:space="preserve">
<value>Specify if SSL is required for your SMTP server</value> <value>Specify if SSL is required for your SMTP server</value>
</data> </data>
<data name="SmptUsername.HelpText" xml:space="preserve"> <data name="SmtpUsername.HelpText" xml:space="preserve">
<value>Enter the username for your SMTP account</value> <value>Enter the username for your SMTP account</value>
</data> </data>
<data name="SmtpPassword.HelpText" xml:space="preserve"> <data name="SmtpPassword.HelpText" xml:space="preserve">
<value>Enter the password for your SMTP account</value> <value>Enter the password for your SMTP account</value>
</data> </data>
<data name="SmptSender.HelpText" xml:space="preserve"> <data name="SmtpSender.HelpText" xml:space="preserve">
<value>Enter the email which emails will be sent from. Please note that this email address may need to be authorized with the SMTP server.</value> <value>Enter the email which emails will be sent from. Please note that this email address may need to be authorized with the SMTP server.</value>
</data> </data>
<data name="EnablePWA.HelpText" xml:space="preserve"> <data name="EnablePWA.HelpText" xml:space="preserve">
@ -243,13 +243,13 @@
<data name="UseSsl.Text" xml:space="preserve"> <data name="UseSsl.Text" xml:space="preserve">
<value>SSL Enabled: </value> <value>SSL Enabled: </value>
</data> </data>
<data name="SmptUsername.Text" xml:space="preserve"> <data name="SmtpUsername.Text" xml:space="preserve">
<value>Username: </value> <value>Username: </value>
</data> </data>
<data name="SmtpPassword.Text" xml:space="preserve"> <data name="SmtpPassword.Text" xml:space="preserve">
<value>Password: </value> <value>Password: </value>
</data> </data>
<data name="SmptSender.Text" xml:space="preserve"> <data name="SmtpSender.Text" xml:space="preserve">
<value>Email Sender: </value> <value>Email Sender: </value>
</data> </data>
<data name="EnablePWA.Text" xml:space="preserve"> <data name="EnablePWA.Text" xml:space="preserve">
@ -339,4 +339,10 @@
<data name="HomePage.Text" xml:space="preserve"> <data name="HomePage.Text" xml:space="preserve">
<value>Home Page:</value> <value>Home Page:</value>
</data> </data>
<data name="SmtpRelay.HelpText" xml:space="preserve">
<value>Only specify this option if you have properly configured an SMTP Relay Service to route your outgoing mail. This option will send notifications from the user's email rather than from the Email Sender specified above.</value>
</data>
<data name="SmtpRelay.Text" xml:space="preserve">
<value>Relay Configured?</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
@ -133,10 +133,10 @@
<value>No Results Returned</value> <value>No Results Returned</value>
</data> </data>
<data name="Tenant.HelpText" xml:space="preserve"> <data name="Tenant.HelpText" xml:space="preserve">
<value>Select the tenant for the SQL server</value> <value>Select the tenant associated with the database server</value>
</data> </data>
<data name="SqlQuery.HelpText" xml:space="preserve"> <data name="SqlQuery.HelpText" xml:space="preserve">
<value>Enter the query for the SQL server</value> <value>Enter the SQL query for the database server</value>
</data> </data>
<data name="SqlQuery.Text" xml:space="preserve"> <data name="SqlQuery.Text" xml:space="preserve">
<value>SQL Query: </value> <value>SQL Query: </value>

View File

@ -210,7 +210,10 @@
<data name="Options.Heading" xml:space="preserve"> <data name="Options.Heading" xml:space="preserve">
<value>Options</value> <value>Options</value>
</data> </data>
<data name="Register" xml:space="preserve"> <data name="Log.Heading" xml:space="preserve">
<value>Log</value>
</data>
<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="Success.Register" xml:space="preserve"> <data name="Success.Register" xml:space="preserve">
@ -276,4 +279,10 @@
<data name="Environment.Text" xml:space="preserve"> <data name="Environment.Text" xml:space="preserve">
<value>Environment:</value> <value>Environment:</value>
</data> </data>
<data name="Log.Text" xml:space="preserve">
<value>Log:</value>
</data>
<data name="Log.HelpText" xml:space="preserve">
<value>System log information for current day</value>
</data>
</root> </root>

View File

@ -120,6 +120,9 @@
<data name="Message.Password.Invalid" xml:space="preserve"> <data name="Message.Password.Invalid" xml:space="preserve">
<value>Passwords Entered Do Not Match</value> <value>Passwords Entered Do Not Match</value>
</data> </data>
<data name="Message.Password.Complexity" xml:space="preserve">
<value>Password Provided Does Not Meet The Complexity Policy</value>
</data>
<data name="From" xml:space="preserve"> <data name="From" xml:space="preserve">
<value>From</value> <value>From</value>
</data> </data>

View File

@ -120,6 +120,9 @@
<data name="Message.Password.NoMatch" xml:space="preserve"> <data name="Message.Password.NoMatch" xml:space="preserve">
<value>Passwords Entered Do Not Match</value> <value>Passwords Entered Do Not Match</value>
</data> </data>
<data name="Message.Password.Complexity" xml:space="preserve">
<value>Password Provided Does Not Meet The Complexity Policy</value>
</data>
<data name="Identity.Name" xml:space="preserve"> <data name="Identity.Name" xml:space="preserve">
<value>Identity</value> <value>Identity</value>
</data> </data>

View File

@ -127,7 +127,7 @@
<value>Error Loading Files</value> <value>Error Loading Files</value>
</data> </data>
<data name="Error.File.Upload" xml:space="preserve"> <data name="Error.File.Upload" xml:space="preserve">
<value>File Upload Failed</value> <value>File Upload Failed Or Is Still In Progress</value>
</data> </data>
<data name="Message.File.NotSelected" xml:space="preserve"> <data name="Message.File.NotSelected" xml:space="preserve">
<value>You Have Not Selected A File To Upload</value> <value>You Have Not Selected A File To Upload</value>

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
@ -124,9 +124,9 @@
<value>User</value> <value>User</value>
</data> </data>
<data name="Username.Enter" xml:space="preserve"> <data name="Username.Enter" xml:space="preserve">
<value>Enter Username</value> <value>Enter User's Name</value>
</data> </data>
<data name="Message.Username.DontExist" xml:space="preserve"> <data name="Message.Username.DontExist" xml:space="preserve">
<value>Username Does Not Exist</value> <value>User Does Not Exist With Name Specified</value>
</data> </data>
</root> </root>

View File

@ -2,27 +2,17 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.JSInterop;
using Oqtane.Documentation; using Oqtane.Documentation;
using Oqtane.Models; using Oqtane.Models;
using Oqtane.Shared; using Oqtane.Shared;
using Oqtane.UI;
namespace Oqtane.Services namespace Oqtane.Services
{ {
[PrivateApi("Don't show in the documentation, as everything should use the Interface")] [PrivateApi("Don't show in the documentation, as everything should use the Interface")]
public class FileService : ServiceBase, IFileService public class FileService : ServiceBase, IFileService
{ {
private readonly SiteState _siteState; public FileService(HttpClient http, SiteState siteState) : base(http, siteState) { }
private readonly IJSRuntime _jsRuntime;
public FileService(HttpClient http, SiteState siteState, IJSRuntime jsRuntime) : base(http, siteState)
{
_siteState = siteState;
_jsRuntime = jsRuntime;
}
private string Apiurl => CreateApiUrl("File"); private string Apiurl => CreateApiUrl("File");
@ -75,54 +65,6 @@ namespace Oqtane.Services
return await GetJsonAsync<File>($"{Apiurl}/upload?url={WebUtility.UrlEncode(url)}&folderid={folderId}&name={name}"); 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)
{
return await UploadFilesAsync(folderId.ToString(), files, id);
}
public async Task<string> UploadFilesAsync(string folder, string[] files, string id)
{
string result = "";
var interop = new Interop(_jsRuntime);
await interop.UploadFiles($"{Apiurl}/upload", folder, id, _siteState.AntiForgeryToken);
// uploading files is asynchronous so we need to wait for the upload to complete
bool success = false;
int attempts = 0;
while (attempts < 5 && success == false)
{
Thread.Sleep(2000); // wait 2 seconds
result = "";
List<File> fileList = await GetFilesAsync(folder);
if (fileList.Count > 0)
{
success = true;
foreach (string file in files)
{
if (!fileList.Exists(item => item.Name == file))
{
success = false;
result += file + ",";
}
}
}
attempts += 1;
}
await interop.SetElementAttribute(id + "ProgressInfo", "style", "display: none;");
await interop.SetElementAttribute(id + "ProgressBar", "style", "display: none;");
if (!success)
{
result = result.Substring(0, result.Length - 1);
}
return result;
}
public async Task<byte[]> DownloadFileAsync(int fileId) public async Task<byte[]> DownloadFileAsync(int fileId)
{ {
return await GetByteArrayAsync($"{Apiurl}/download/{fileId}"); return await GetByteArrayAsync($"{Apiurl}/download/{fileId}");

View File

@ -66,27 +66,6 @@ namespace Oqtane.Services
/// <returns></returns> /// <returns></returns>
Task<File> UploadFileAsync(string url, int folderId, string name); Task<File> UploadFileAsync(string url, int folderId, string name);
/// <summary>
/// Upload one or more files.
/// </summary>
/// <param name="folderId">Target <see cref="Folder"/></param>
/// <param name="files">The files to upload, serialized as a string.</param>
/// <param name="fileUploadName">A task-identifier, to ensure communication about this upload.</param>
/// <returns></returns>
Task<string> UploadFilesAsync(int folderId, string[] files, string fileUploadName);
/// <summary>
/// Upload one or more files.
/// </summary>
/// <param name="folder">Target <see cref="Folder"/>
/// TODO: todoc verify exactly from where the folder path must start
/// </param>
/// <param name="files">The files to upload, serialized as a string.</param>
/// <param name="fileUploadName">A task-identifier, to ensure communication about this upload.</param>
/// <returns></returns>
Task<string> UploadFilesAsync(string folder, string[] files, string fileUploadName);
/// <summary> /// <summary>
/// Get / download a file (the body). /// Get / download a file (the body).
/// </summary> /// </summary>

View File

@ -13,6 +13,6 @@ namespace Oqtane.Services
/// Returns a collection of supported cultures /// Returns a collection of supported cultures
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
Task<IEnumerable<Culture>> GetCulturesAsync(); Task<IEnumerable<Culture>> GetCulturesAsync(bool installed);
} }
} }

View File

@ -164,6 +164,24 @@ namespace Oqtane.Services
/// <returns></returns> /// <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="entityName"></param>
/// <param name="entityId"></param>
/// <param name="settingName"></param>
/// <returns></returns>
Task DeleteSettingAsync(string entityName, int entityId, string settingName);
/// <summary>
/// Returns a specific setting
/// </summary>
/// <param name="entityName"></param>
/// <param name="entityId"></param>
/// <param name="settingName"></param>
/// <returns></returns>
Task<List<Setting>> GetSettingsAsync(string entityName, int entityId, string settingName);
/// <summary> /// <summary>
/// Returns a specific setting /// Returns a specific setting
/// </summary> /// </summary>

View File

@ -14,6 +14,6 @@ namespace Oqtane.Services
private string Apiurl => CreateApiUrl("Localization"); private string Apiurl => CreateApiUrl("Localization");
public async Task<IEnumerable<Culture>> GetCulturesAsync() => await GetJsonAsync<IEnumerable<Culture>>(Apiurl); public async Task<IEnumerable<Culture>> GetCulturesAsync(bool installed) => await GetJsonAsync<IEnumerable<Culture>>($"{Apiurl}?installed={installed}");
} }
} }

View File

@ -170,6 +170,26 @@ namespace Oqtane.Services
} }
} }
public async Task DeleteSettingAsync(string entityName, int entityId, string settingName)
{
var settings = await GetJsonAsync<List<Setting>>($"{Apiurl}?entityname={entityName}&entityid={entityId}");
var setting = settings.FirstOrDefault(item => item.SettingName == settingName);
if (setting != null)
{
await DeleteAsync($"{Apiurl}/{setting.SettingId}/{entityName}");
}
}
public async Task<List<Setting>> GetSettingsAsync(string entityName, int entityId, string settingName)
{
var settings = await GetJsonAsync<List<Setting>>($"{Apiurl}?entityname={entityName}&entityid={entityId}");
if (!string.IsNullOrEmpty(settingName))
{
settings = settings.Where(item => item.SettingName == settingName).ToList();
}
return settings;
}
public async Task<Setting> GetSettingAsync(string entityName, int settingId) public async Task<Setting> GetSettingAsync(string entityName, int settingId)
{ {
return await GetJsonAsync<Setting>($"{Apiurl}/{settingId}/{entityName}"); return await GetJsonAsync<Setting>($"{Apiurl}/{settingId}/{entityName}");

View File

@ -33,7 +33,7 @@
} }
} }
@if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.Permissions)) @if (_canViewAdminDashboard || UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.Permissions))
{ {
<button type="button" class="btn @ButtonClass" data-bs-toggle="offcanvas" data-bs-target="#offcanvasControlPanel" aria-controls="offcanvasControlPanel"> <button type="button" class="btn @ButtonClass" data-bs-toggle="offcanvas" data-bs-target="#offcanvasControlPanel" aria-controls="offcanvasControlPanel">
<span class="oi oi-cog"></span> <span class="oi oi-cog"></span>
@ -46,16 +46,17 @@
</div> </div>
<div class="@BodyClass"> <div class="@BodyClass">
<div class="container-fluid"> <div class="container-fluid">
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin)) @if (_canViewAdminDashboard)
{ {
<div class="row d-flex"> <div class="row d-flex">
<div class="col"> <div class="col">
<button type="button" data-bs-dismiss="offcanvas" class="btn btn-primary col-12" @onclick=@(async () => Navigate("Admin"))>@Localizer["AdminDash"]</button> <button type="button" data-bs-dismiss="offcanvas" class="btn btn-primary col-12" @onclick=@(async () => Navigate("Admin"))>@Localizer["AdminDash"]</button>
</div> </div>
</div> </div>
<hr class="app-rule" /> <hr class="app-rule" />
}
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
{
<div class="row"> <div class="row">
<div class="col text-center"> <div class="col text-center">
<label class="control-label">@Localizer["Page.Manage"] </label> <label class="control-label">@Localizer["Page.Manage"] </label>
@ -80,144 +81,149 @@
} }
</div> </div>
</div> </div>
} <hr class="app-rule" />
@if (_deleteConfirmation) @if (_deleteConfirmation)
{ {
<div class="app-admin-modal"> <div class="app-admin-modal">
<div class="modal" tabindex="-1" role="dialog"> <div class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 class="modal-title">@Localizer["Page.Delete"]</h5> <h5 class="modal-title">@Localizer["Page.Delete"]</h5>
<button type="button" class="btn-close" aria-label="Close" @onclick="ConfirmDelete"></button> <button type="button" class="btn-close" aria-label="Close" @onclick="ConfirmDelete"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<p>Are You Sure You Want To Delete This Page?</p> <p>Are You Sure You Want To Delete This Page?</p>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-danger" @onclick="DeletePage">@SharedLocalizer["Delete"]</button> <button type="button" class="btn btn-danger" @onclick="DeletePage">@SharedLocalizer["Delete"]</button>
<button type="button" class="btn btn-secondary" @onclick="ConfirmDelete">@SharedLocalizer["Cancel"]</button> <button type="button" class="btn btn-secondary" @onclick="ConfirmDelete">@SharedLocalizer["Cancel"]</button>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> }
} }
<hr class="app-rule" />
<div class="row"> @if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.Permissions))
<div class="col text-center">
<label for="Module" class="control-label">@Localizer["Module.Manage"] </label>
<select class="form-select" @bind="@ModuleType">
<option value="new">@Localizer["Module.AddNew"]</option>
<option value="existing">@Localizer["Module.AddExisting"]</option>
</select>
@if (ModuleType == "new")
{
@if (_moduleDefinitions != null)
{
<select class="form-select" @onchange="(e => CategoryChanged(e))">
@foreach (var category in _categories)
{
if (category == Category)
{
<option value="@category" selected>@category @Localizer["Modules"]</option>
}
else
{
<option value="@category">@category @Localizer["Modules"]</option>
}
}
</select>
<select class="form-select" @onchange="(e => ModuleChanged(e))">
@if (ModuleDefinitionName == "-")
{
<option value="-" selected>&lt;@Localizer["Module.Select"]&gt;</option>
}
else
{
<option value="-">&lt;@Localizer["Module.Select"]&gt;</option>
}
@foreach (var moduledefinition in _moduleDefinitions)
{
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Utilize, moduledefinition.Permissions))
{
if (moduledefinition.Runtimes == "" || moduledefinition.Runtimes.Contains(PageState.Runtime.ToString()))
{
<option value="@moduledefinition.ModuleDefinitionName">@moduledefinition.Name</option>
}
}
}
</select>
}
}
else
{
<select class="form-select" @onchange="(e => PageChanged(e))">
<option value="-">&lt;@Localizer["Page.Select"]&gt;</option>
@foreach (Page p in _pages)
{
<option value="@p.PageId">@p.Name</option>
}
</select>
<select class="form-select" @bind="@ModuleId">
<option value="-">&lt;@Localizer["Module.Select"]&gt;</option>
@foreach (Module module in _modules)
{
<option value="@module.ModuleId">@module.Title</option>
}
</select>
}
</div>
</div>
<div class="row">
<div class="col text-center">
<label for="Title" class="control-label">@Localizer["Title"] </label>
<input type="text" name="Title" class="form-control" @bind="@Title" />
</div>
</div>
@if (_pane.Length > 1)
{ {
<div class="row"> <div class="row">
<div class="col text-center"> <div class="col text-center">
<label for="Pane" class="control-label">@Localizer["Pane"] </label> <label for="Module" class="control-label">@Localizer["Module.Manage"] </label>
<select class="form-select" @bind="@Pane"> <select class="form-select" @bind="@ModuleType">
@foreach (string pane in PageState.Page.Panes) <option value="new">@Localizer["Module.AddNew"]</option>
<option value="existing">@Localizer["Module.AddExisting"]</option>
</select>
@if (ModuleType == "new")
{
@if (_moduleDefinitions != null)
{ {
<option value="@pane">@pane Pane</option> <select class="form-select" @onchange="(e => CategoryChanged(e))">
@foreach (var category in _categories)
{
if (category == Category)
{
<option value="@category" selected>@category @Localizer["Modules"]</option>
}
else
{
<option value="@category">@category @Localizer["Modules"]</option>
}
}
</select>
<select class="form-select" @onchange="(e => ModuleChanged(e))">
@if (ModuleDefinitionName == "-")
{
<option value="-" selected>&lt;@Localizer["Module.Select"]&gt;</option>
}
else
{
<option value="-">&lt;@Localizer["Module.Select"]&gt;</option>
}
@foreach (var moduledefinition in _moduleDefinitions)
{
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Utilize, moduledefinition.Permissions))
{
if (moduledefinition.Runtimes == "" || moduledefinition.Runtimes.Contains(PageState.Runtime.ToString()))
{
<option value="@moduledefinition.ModuleDefinitionName">@moduledefinition.Name</option>
}
}
}
</select>
}
}
else
{
<select class="form-select" @onchange="(e => PageChanged(e))">
<option value="-">&lt;@Localizer["Page.Select"]&gt;</option>
@foreach (Page p in _pages)
{
<option value="@p.PageId">@p.Name</option>
}
</select>
<select class="form-select" @bind="@ModuleId">
<option value="-">&lt;@Localizer["Module.Select"]&gt;</option>
@foreach (Module module in _modules)
{
<option value="@module.ModuleId">@module.Title</option>
}
</select>
}
</div>
</div>
<div class="row">
<div class="col text-center">
<label for="Title" class="control-label">@Localizer["Title"] </label>
<input type="text" name="Title" class="form-control" @bind="@Title" />
</div>
</div>
@if (_pane.Length > 1)
{
<div class="row">
<div class="col text-center">
<label for="Pane" class="control-label">@Localizer["Pane"] </label>
<select class="form-select" @bind="@Pane">
@foreach (string pane in PageState.Page.Panes)
{
<option value="@pane">@pane Pane</option>
}
</select>
</div>
</div>
}
<div class="row">
<div class="col text-center">
<label for="Container" class="control-label">@Localizer["Container"] </label>
<select class="form-select" @bind="@ContainerType">
@foreach (var container in _containers)
{
<option value="@container.TypeName">@container.Name</option>
} }
</select> </select>
</div> </div>
</div> </div>
<div class="row">
<div class="col text-center">
<label for="visibility" class="control-label">@Localizer["Visibility"]</label>
<select class="form-select" @bind="@Visibility">
<option value="view">@Localizer["VisibilityView"]</option>
<option value="edit">@Localizer["VisibilityEdit"]</option>
</select>
</div>
</div>
<button type="button" class="btn btn-primary col-12 mt-4" @onclick="@AddModule">@Localizer["Page.Module.Add"]</button>
@((MarkupString)Message)
} }
<div class="row">
<div class="col text-center">
<label for="Container" class="control-label">@Localizer["Container"] </label>
<select class="form-select" @bind="@ContainerType">
@foreach (var container in _containers)
{
<option value="@container.TypeName">@container.Name</option>
}
</select>
</div>
</div>
<div class="row">
<div class="col text-center">
<label for="visibility" class="control-label">@Localizer["Visibility"]</label>
<select class="form-select" @bind="@Visibility">
<option value="edit" selected>@Localizer["VisibilityEdit"]</option>
<option value="view">@Localizer["VisibilityView"]</option>
</select>
</div>
</div>
<button type="button" class="btn btn-primary col-12 mt-4" @onclick="@AddModule">@Localizer["Page.Module.Add"]</button>
@((MarkupString) Message)
</div> </div>
</div> </div>
</div> </div>
} }
@code{ @code{
private bool _canViewAdminDashboard = false;
private bool _showEditMode = false; private bool _showEditMode = false;
private bool _deleteConfirmation = false; private bool _deleteConfirmation = false;
private List<string> _categories = new List<string>(); private List<string> _categories = new List<string>();
@ -265,7 +271,7 @@
protected string Title { get; private set; } = ""; protected string Title { get; private set; } = "";
protected string ContainerType { get; private set; } = ""; protected string ContainerType { get; private set; } = "";
protected string Visibility { get; private set; } = "edit"; protected string Visibility { get; private set; } = "view";
protected string Message { get; private set; } = ""; protected string Message { get; private set; } = "";
[Parameter] [Parameter]
@ -286,6 +292,7 @@
protected override async Task OnParametersSetAsync() protected override async Task OnParametersSetAsync()
{ {
_canViewAdminDashboard = CanViewAdminDashboard();
_showEditMode = false; _showEditMode = false;
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.Permissions)) if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.Permissions))
{ {
@ -321,6 +328,22 @@
} }
} }
private bool CanViewAdminDashboard()
{
var admin = PageState.Pages.FirstOrDefault(item => item.Path == "admin");
if (admin != null)
{
foreach (var page in PageState.Pages.Where(item => item.ParentId == admin?.PageId))
{
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, page.Permissions))
{
return true;
}
}
}
return false;
}
private void CategoryChanged(ChangeEventArgs e) private void CategoryChanged(ChangeEventArgs e)
{ {
Category = (string)e.Value; Category = (string)e.Value;
@ -380,6 +403,8 @@
// set module view permissions to page edit permissions // set module view permissions to page edit permissions
permissions.Find(p => p.PermissionName == PermissionNames.View).Permissions = permissions.Find(p => p.PermissionName == PermissionNames.Edit).Permissions; permissions.Find(p => p.PermissionName == PermissionNames.View).Permissions = permissions.Find(p => p.PermissionName == PermissionNames.Edit).Permissions;
} }
// set entityname
permissions.ForEach(item => item.EntityName = EntityNames.Module);
module.Permissions = UserSecurity.SetPermissionStrings(permissions); module.Permissions = UserSecurity.SetPermissionStrings(permissions);
module = await ModuleService.AddModuleAsync(module); module = await ModuleService.AddModuleAsync(module);
@ -465,12 +490,10 @@
case "Admin": case "Admin":
// get admin dashboard moduleid // get admin dashboard moduleid
module = PageState.Modules.FirstOrDefault(item => item.ModuleDefinitionName == Constants.AdminDashboardModule); module = PageState.Modules.FirstOrDefault(item => item.ModuleDefinitionName == Constants.AdminDashboardModule);
if (module != null) if (module != null)
{ {
NavigationManager.NavigateTo(EditUrl(PageState.Page.Path, module.ModuleId, "Index", "")); NavigationManager.NavigateTo(EditUrl(PageState.Page.Path, module.ModuleId, "Index", ""));
} }
break; break;
case "Add": case "Add":
case "Edit": case "Edit":
@ -551,19 +574,19 @@
{ {
page.IsDeleted = true; page.IsDeleted = true;
await PageService.UpdatePageAsync(page); await PageService.UpdatePageAsync(page);
await logger.Log(page.PageId, null, PageState.User.UserId, GetType().AssemblyQualifiedName, "ControlPanel", LogFunction.Delete, LogLevel.Information, null, "Page Deleted {Page}", page); await logger.Log(page.PageId, null, PageState.User?.UserId, GetType().AssemblyQualifiedName, "ControlPanel", LogFunction.Delete, LogLevel.Information, null, "Page Deleted {Page}", page);
NavigationManager.NavigateTo(NavigateUrl("")); NavigationManager.NavigateTo(NavigateUrl(""));
} }
else // personalized page else // personalized page
{ {
await PageService.DeletePageAsync(page.PageId); await PageService.DeletePageAsync(page.PageId);
await logger.Log(page.PageId, null, PageState.User.UserId, GetType().AssemblyQualifiedName, "ControlPanel", LogFunction.Delete, LogLevel.Information, null, "Page Deleted {Page}", page); await logger.Log(page.PageId, null, PageState.User?.UserId, GetType().AssemblyQualifiedName, "ControlPanel", LogFunction.Delete, LogLevel.Information, null, "Page Deleted {Page}", page);
NavigationManager.NavigateTo(NavigateUrl()); NavigationManager.NavigateTo(NavigateUrl());
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
await logger.Log(page.PageId, null, PageState.User.UserId, GetType().AssemblyQualifiedName, "ControlPanel", LogFunction.Delete, LogLevel.Information, ex, "Page Deleted {Page} {Error}", page, ex.Message); await logger.Log(page.PageId, null, PageState.User?.UserId, GetType().AssemblyQualifiedName, "ControlPanel", LogFunction.Delete, LogLevel.Information, ex, "Page Deleted {Page} {Error}", page, ex.Message);
} }
} }
@ -596,8 +619,8 @@
private async Task UpdateSettingsAsync() private async Task UpdateSettingsAsync()
{ {
Dictionary<string, string> settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId); Dictionary<string, string> settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId);
SettingService.SetSetting(settings, settingCategory, _category); settings = SettingService.SetSetting(settings, settingCategory, _category);
SettingService.SetSetting(settings, settingPane, _pane); settings = SettingService.SetSetting(settings, settingPane, _pane);
await SettingService.UpdateUserSettingsAsync(settings, PageState.User.UserId); await SettingService.UpdateUserSettingsAsync(settings, PageState.User.UserId);
} }

View File

@ -32,7 +32,7 @@ namespace Oqtane.Themes.Controls
protected async Task LogoutUser() protected async Task LogoutUser()
{ {
await LoggingService.Log(PageState.Alias, PageState.Page.PageId, null, PageState.User.UserId, GetType().AssemblyQualifiedName, "Logout", LogFunction.Security, LogLevel.Information, null, "User Logout For Username {Username}", PageState.User.Username); await LoggingService.Log(PageState.Alias, PageState.Page.PageId, null, PageState.User?.UserId, GetType().AssemblyQualifiedName, "Logout", LogFunction.Security, LogLevel.Information, null, "User Logout For Username {Username}", PageState.User?.Username);
// check if anonymous user can access page // check if anonymous user can access page
var url = PageState.Alias.Path + "/" + PageState.Page.Path; var url = PageState.Alias.Path + "/" + PageState.Page.Path;

View File

@ -32,15 +32,24 @@ namespace Oqtane.Themes
{ {
if (Resources != null && Resources.Exists(item => item.ResourceType == ResourceType.Script)) if (Resources != null && Resources.Exists(item => item.ResourceType == ResourceType.Script))
{ {
var interop = new Interop(JSRuntime);
var scripts = new List<object>(); var scripts = new List<object>();
var inline = 0;
foreach (Resource resource in Resources.Where(item => item.ResourceType == ResourceType.Script)) foreach (Resource resource in Resources.Where(item => item.ResourceType == ResourceType.Script))
{ {
var url = (resource.Url.Contains("://")) ? resource.Url : PageState.Alias.BaseUrl + resource.Url; if (!string.IsNullOrEmpty(resource.Url))
scripts.Add(new { href = url, bundle = resource.Bundle ?? "", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", es6module = resource.ES6Module }); {
var url = (resource.Url.Contains("://")) ? resource.Url : PageState.Alias.BaseUrl + resource.Url;
scripts.Add(new { href = url, bundle = resource.Bundle ?? "", integrity = resource.Integrity ?? "", crossorigin = resource.CrossOrigin ?? "", es6module = resource.ES6Module });
}
else
{
inline += 1;
await interop.IncludeScript(GetType().Namespace.ToLower() + inline.ToString(), "", "", "", resource.Content, resource.Location.ToString().ToLower());
}
} }
if (scripts.Any()) if (scripts.Any())
{ {
var interop = new Interop(JSRuntime);
await interop.IncludeScripts(scripts.ToArray()); await interop.IncludeScripts(scripts.ToArray());
} }
} }

View File

@ -60,13 +60,13 @@ namespace Oqtane.UI
} }
} }
public Task IncludeMeta(string id, string attribute, string name, string content, string key) public Task IncludeMeta(string id, string attribute, string name, string content)
{ {
try try
{ {
_jsRuntime.InvokeVoidAsync( _jsRuntime.InvokeVoidAsync(
"Oqtane.Interop.includeMeta", "Oqtane.Interop.includeMeta",
id, attribute, name, content, key); id, attribute, name, content);
return Task.CompletedTask; return Task.CompletedTask;
} }
catch catch
@ -105,6 +105,7 @@ namespace Oqtane.UI
} }
} }
// external scripts need to specify src, inline scripts need to specify id and content
public Task IncludeScript(string id, string src, string integrity, string crossorigin, string content, string location) public Task IncludeScript(string id, string src, string integrity, string crossorigin, string content, string location)
{ {
try try

View File

@ -87,10 +87,9 @@ else
// retrieve friendly localized error // retrieve friendly localized error
_error = Localizer["Error.Module.Exception"]; _error = Localizer["Error.Module.Exception"];
// log error // log error
int? userId = (PageState.User != null) ? PageState.User.UserId : null;
string category = GetType().AssemblyQualifiedName; string category = GetType().AssemblyQualifiedName;
string feature = Utilities.GetTypeNameLastSegment(category, 1); string feature = Utilities.GetTypeNameLastSegment(category, 1);
await LoggingService.Log(null, ModuleState.PageId, ModuleState.ModuleId, userId, category, feature, LogFunction.Other, LogLevel.Error, exception, "An Unexpected Error Has Occurred In {ModuleDefinitionName}: {Error}", ModuleState.ModuleDefinitionName, exception.Message); await LoggingService.Log(null, ModuleState.PageId, ModuleState.ModuleId, PageState.User?.UserId, category, feature, LogFunction.Other, LogLevel.Error, exception, "An Unexpected Error Has Occurred In {ModuleDefinitionName}: {Error}", ModuleState.ModuleDefinitionName, exception.Message);
await base.OnErrorAsync(exception); await base.OnErrorAsync(exception);
return; return;
} }

View File

@ -97,7 +97,7 @@
// reload the client application from the server if there is a forced reload or the user navigated to a site with a different alias // reload the client application from the server if there is a forced reload or the user navigated to a site with a different alias
if (querystring.ContainsKey("reload") || (!NavigationManager.ToBaseRelativePath(_absoluteUri).ToLower().StartsWith(SiteState.Alias.Path.ToLower()) && !string.IsNullOrEmpty(SiteState.Alias.Path))) if (querystring.ContainsKey("reload") || (!NavigationManager.ToBaseRelativePath(_absoluteUri).ToLower().StartsWith(SiteState.Alias.Path.ToLower()) && !string.IsNullOrEmpty(SiteState.Alias.Path)))
{ {
if (querystring["reload"] == "post") if (querystring.ContainsKey("reload") && querystring["reload"] == "post")
{ {
// post back so that the cookies are set correctly - required on any change to the principal // post back so that the cookies are set correctly - required on any change to the principal
var interop = new Interop(JSRuntime); var interop = new Interop(JSRuntime);

View File

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<Version>3.2.1</Version> <Version>3.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>
@ -10,7 +10,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/v3.2.1</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.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>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>Oqtane.Database.MySQL</id> <id>Oqtane.Database.MySQL</id>
<version>3.2.1</version> <version>3.3.1</version>
<authors>Shaun Walker</authors> <authors>Shaun Walker</authors>
<owners>.NET Foundation</owners> <owners>.NET Foundation</owners>
<title>Oqtane MySQL Provider</title> <title>Oqtane MySQL Provider</title>
@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license> <license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl> <projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.3.1</releaseNotes>
<icon>icon.png</icon> <icon>icon.png</icon>
<tags>oqtane</tags> <tags>oqtane</tags>
</metadata> </metadata>

View File

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<Version>3.2.1</Version> <Version>3.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>
@ -10,7 +10,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/v3.2.1</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.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>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>Oqtane.Database.PostgreSQL</id> <id>Oqtane.Database.PostgreSQL</id>
<version>3.2.1</version> <version>3.3.1</version>
<authors>Shaun Walker</authors> <authors>Shaun Walker</authors>
<owners>.NET Foundation</owners> <owners>.NET Foundation</owners>
<title>Oqtane PostgreSQL Provider</title> <title>Oqtane PostgreSQL Provider</title>
@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license> <license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl> <projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.3.1</releaseNotes>
<icon>icon.png</icon> <icon>icon.png</icon>
<tags>oqtane</tags> <tags>oqtane</tags>
</metadata> </metadata>

View File

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<Version>3.2.1</Version> <Version>3.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>
@ -10,7 +10,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/v3.2.1</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.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>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>Oqtane.Database.SqlServer</id> <id>Oqtane.Database.SqlServer</id>
<version>3.2.1</version> <version>3.3.1</version>
<authors>Shaun Walker</authors> <authors>Shaun Walker</authors>
<owners>.NET Foundation</owners> <owners>.NET Foundation</owners>
<title>Oqtane SQL Server Provider</title> <title>Oqtane SQL Server Provider</title>
@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license> <license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl> <projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.3.1</releaseNotes>
<icon>icon.png</icon> <icon>icon.png</icon>
<tags>oqtane</tags> <tags>oqtane</tags>
</metadata> </metadata>

View File

@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<Version>3.2.1</Version> <Version>3.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>
@ -10,7 +10,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/v3.2.1</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.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>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>Oqtane.Database.Sqlite</id> <id>Oqtane.Database.Sqlite</id>
<version>3.2.1</version> <version>3.3.1</version>
<authors>Shaun Walker</authors> <authors>Shaun Walker</authors>
<owners>.NET Foundation</owners> <owners>.NET Foundation</owners>
<title>Oqtane SQLite Provider</title> <title>Oqtane SQLite Provider</title>
@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license> <license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl> <projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.3.1</releaseNotes>
<icon>icon.png</icon> <icon>icon.png</icon>
<tags>oqtane</tags> <tags>oqtane</tags>
</metadata> </metadata>

View File

@ -12,7 +12,7 @@ namespace Oqtane.Maui;
public static class MauiProgram public static class MauiProgram
{ {
// the API service url // the API service url
static string apiurl = "https://www.oqtane.org"; // for testing static string apiurl = "https://www.dnfprojects.com"; // for testing
//static string apiurl = "http://localhost:44357"; // for local development (Oqtane.Server must be already running for MAUI client to connect) //static string apiurl = "http://localhost:44357"; // for local development (Oqtane.Server must be already running for MAUI client to connect)
public static MauiApp CreateMauiApp() public static MauiApp CreateMauiApp()

View File

@ -6,7 +6,7 @@
<!-- Uncomment to also build the tizen app. You will need to install tizen by following this: https://github.com/Samsung/Tizen.NET --> <!-- Uncomment to also build the tizen app. You will need to install tizen by following this: https://github.com/Samsung/Tizen.NET -->
<!-- <TargetFrameworks>$(TargetFrameworks);net6.0-tizen</TargetFrameworks> --> <!-- <TargetFrameworks>$(TargetFrameworks);net6.0-tizen</TargetFrameworks> -->
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<Version>3.2.1</Version> <Version>3.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>
@ -14,7 +14,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/v3.2.1</PackageReleaseNotes> <PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.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.Maui</RootNamespace> <RootNamespace>Oqtane.Maui</RootNamespace>
@ -31,7 +31,7 @@
<ApplicationIdGuid>0E29FC31-1B83-48ED-B6E0-9F3C67B775D4</ApplicationIdGuid> <ApplicationIdGuid>0E29FC31-1B83-48ED-B6E0-9F3C67B775D4</ApplicationIdGuid>
<!-- Versions --> <!-- Versions -->
<ApplicationDisplayVersion>3.2.1</ApplicationDisplayVersion> <ApplicationDisplayVersion>3.3.1</ApplicationDisplayVersion>
<ApplicationVersion>1</ApplicationVersion> <ApplicationVersion>1</ApplicationVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">14.2</SupportedOSPlatformVersion> <SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">14.2</SupportedOSPlatformVersion>
@ -71,16 +71,8 @@
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.Localization" Version="6.0.3" /> <PackageReference Include="Microsoft.Extensions.Localization" Version="6.0.3" />
<PackageReference Include="System.Net.Http.Json" Version="6.0.0" /> <PackageReference Include="System.Net.Http.Json" Version="6.0.0" />
<PackageReference Include="Oqtane.Client" Version="3.3.1" />
<PackageReference Include="Oqtane.Shared" Version="3.3.1" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Reference Include="Oqtane.Client">
<HintPath>..\Oqtane.Server\bin\Debug\net6.0\Oqtane.Client.dll</HintPath>
</Reference>
<Reference Include="Oqtane.Shared">
<HintPath>..\Oqtane.Server\bin\Debug\net6.0\Oqtane.Shared.dll</HintPath>
</Reference>
</ItemGroup>
</Project> </Project>

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>Oqtane.Client</id> <id>Oqtane.Client</id>
<version>3.2.1</version> <version>3.3.1</version>
<authors>Shaun Walker</authors> <authors>Shaun Walker</authors>
<owners>.NET Foundation</owners> <owners>.NET Foundation</owners>
<title>Oqtane Framework</title> <title>Oqtane Framework</title>
@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license> <license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl> <projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.3.1</releaseNotes>
<icon>icon.png</icon> <icon>icon.png</icon>
<tags>oqtane</tags> <tags>oqtane</tags>
</metadata> </metadata>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>Oqtane.Framework</id> <id>Oqtane.Framework</id>
<version>3.2.1</version> <version>3.3.1</version>
<authors>Shaun Walker</authors> <authors>Shaun Walker</authors>
<owners>.NET Foundation</owners> <owners>.NET Foundation</owners>
<title>Oqtane Framework</title> <title>Oqtane Framework</title>
@ -11,8 +11,8 @@
<copyright>.NET Foundation</copyright> <copyright>.NET Foundation</copyright>
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license> <license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework/releases/download/v3.2.1/Oqtane.Framework.3.2.1.Upgrade.zip</projectUrl> <projectUrl>https://github.com/oqtane/oqtane.framework/releases/download/v3.3.1/Oqtane.Framework.3.3.1.Upgrade.zip</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.3.1</releaseNotes>
<icon>icon.png</icon> <icon>icon.png</icon>
<tags>oqtane framework</tags> <tags>oqtane framework</tags>
</metadata> </metadata>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>Oqtane.Server</id> <id>Oqtane.Server</id>
<version>3.2.1</version> <version>3.3.1</version>
<authors>Shaun Walker</authors> <authors>Shaun Walker</authors>
<owners>.NET Foundation</owners> <owners>.NET Foundation</owners>
<title>Oqtane Framework</title> <title>Oqtane Framework</title>
@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license> <license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl> <projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.3.1</releaseNotes>
<icon>icon.png</icon> <icon>icon.png</icon>
<tags>oqtane</tags> <tags>oqtane</tags>
</metadata> </metadata>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>Oqtane.Shared</id> <id>Oqtane.Shared</id>
<version>3.2.1</version> <version>3.3.1</version>
<authors>Shaun Walker</authors> <authors>Shaun Walker</authors>
<owners>.NET Foundation</owners> <owners>.NET Foundation</owners>
<title>Oqtane Framework</title> <title>Oqtane Framework</title>
@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license> <license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl> <projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.3.1</releaseNotes>
<icon>icon.png</icon> <icon>icon.png</icon>
<tags>oqtane</tags> <tags>oqtane</tags>
</metadata> </metadata>

View File

@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata> <metadata>
<id>Oqtane.Updater</id> <id>Oqtane.Updater</id>
<version>3.2.1</version> <version>3.3.1</version>
<authors>Shaun Walker</authors> <authors>Shaun Walker</authors>
<owners>.NET Foundation</owners> <owners>.NET Foundation</owners>
<title>Oqtane Framework</title> <title>Oqtane Framework</title>
@ -12,7 +12,7 @@
<requireLicenseAcceptance>false</requireLicenseAcceptance> <requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license> <license type="expression">MIT</license>
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl> <projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.2.1</releaseNotes> <releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v3.3.1</releaseNotes>
<icon>icon.png</icon> <icon>icon.png</icon>
<tags>oqtane</tags> <tags>oqtane</tags>
</metadata> </metadata>

View File

@ -1 +1 @@
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net6.0\publish\*" -DestinationPath "Oqtane.Framework.3.2.1.Install.zip" -Force Compress-Archive -Path "..\Oqtane.Server\bin\Release\net6.0\publish\*" -DestinationPath "Oqtane.Framework.3.3.1.Install.zip" -Force

View File

@ -13,6 +13,23 @@ rmdir /Q/S "..\Oqtane.Server\bin\Release\net6.0\publish"
dotnet publish ..\Oqtane.Server\Oqtane.Server.csproj /p:Configuration=Release dotnet publish ..\Oqtane.Server\Oqtane.Server.csproj /p:Configuration=Release
del /F/Q/S "..\Oqtane.Server\bin\Release\net6.0\publish\wwwroot\Content" > NUL del /F/Q/S "..\Oqtane.Server\bin\Release\net6.0\publish\wwwroot\Content" > NUL
rmdir /Q/S "..\Oqtane.Server\bin\Release\net6.0\publish\wwwroot\Content" rmdir /Q/S "..\Oqtane.Server\bin\Release\net6.0\publish\wwwroot\Content"
setlocal ENABLEDELAYEDEXPANSION
set retain=Oqtane.Modules.Admin.Login,Oqtane.Modules.HtmlText,Templates
for /D %%i in ("..\Oqtane.Server\bin\Release\net6.0\publish\wwwroot\Modules\*") do (
set /A found=0
for %%j in (%retain%) do (
if "%%~nxi" == "%%j" set /A found=1
)
if not !found! == 1 rmdir /Q/S "%%i"
)
set retain=Oqtane.Themes.BlazorTheme,Oqtane.Themes.OqtaneTheme,Templates
for /D %%i in ("..\Oqtane.Server\bin\Release\net6.0\publish\wwwroot\Themes\*") do (
set /A found=0
for %%j in (%retain%) do (
if "%%~nxi" == "%%j" set /A found=1
)
if not !found! == 1 rmdir /Q/S "%%i"
)
del "..\Oqtane.Server\bin\Release\net6.0\publish\appsettings.json" del "..\Oqtane.Server\bin\Release\net6.0\publish\appsettings.json"
ren "..\Oqtane.Server\bin\Release\net6.0\publish\appsettings.release.json" "appsettings.json" ren "..\Oqtane.Server\bin\Release\net6.0\publish\appsettings.release.json" "appsettings.json"
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe ".\install.ps1" C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe ".\install.ps1"

View File

@ -1 +1 @@
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net6.0\publish\*" -DestinationPath "Oqtane.Framework.3.2.1.Upgrade.zip" -Force Compress-Archive -Path "..\Oqtane.Server\bin\Release\net6.0\publish\*" -DestinationPath "Oqtane.Framework.3.3.1.Upgrade.zip" -Force

View File

@ -20,6 +20,7 @@ using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.Formats.Png;
using System.Net.Http; using System.Net.Http;
using Oqtane.Migrations.Tenant;
// ReSharper disable StringIndexOfIsCultureSpecific.1 // ReSharper disable StringIndexOfIsCultureSpecific.1
@ -135,7 +136,9 @@ namespace Oqtane.Controllers
public Models.File Put(int id, [FromBody] Models.File file) public Models.File Put(int id, [FromBody] Models.File file)
{ {
var File = _files.GetFile(file.FileId, false); var File = _files.GetFile(file.FileId, false);
if (ModelState.IsValid && File != null && File.Folder.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, EntityNames.Folder, file.FolderId, PermissionNames.Edit)) if (ModelState.IsValid && file.Folder.SiteId == _alias.SiteId && File != null // ensure file exists
&& _userPermissions.IsAuthorized(User, file.Folder.SiteId, EntityNames.Folder, File.FolderId, PermissionNames.Edit) // ensure user had edit rights to original folder
&& _userPermissions.IsAuthorized(User, file.Folder.SiteId, EntityNames.Folder, file.FolderId, PermissionNames.Edit)) // ensure user has edit rights to new folder
{ {
if (File.Name != file.Name || File.FolderId != file.FolderId) if (File.Name != file.Name || File.FolderId != file.FolderId)
{ {
@ -148,7 +151,15 @@ namespace Oqtane.Controllers
System.IO.File.Move(_files.GetFilePath(File), Path.Combine(folderpath, file.Name)); System.IO.File.Move(_files.GetFilePath(File), Path.Combine(folderpath, file.Name));
} }
file.Extension = Path.GetExtension(file.Name).ToLower().Replace(".", ""); var newfile = CreateFile(file.Name, file.Folder.FolderId, _files.GetFilePath(file));
if (newfile != null)
{
file.Extension = newfile.Extension;
file.Size = newfile.Size;
file.ImageWidth = newfile.ImageWidth;
file.ImageHeight = newfile.ImageHeight;
}
file = _files.UpdateFile(file); file = _files.UpdateFile(file);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.File, file.FileId, SyncEventActions.Update); _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.File, file.FileId, SyncEventActions.Update);
_logger.Log(LogLevel.Information, this, LogFunction.Update, "File Updated {File}", file); _logger.Log(LogLevel.Information, this, LogFunction.Update, "File Updated {File}", file);
@ -169,7 +180,7 @@ namespace Oqtane.Controllers
public void Delete(int id) public void Delete(int id)
{ {
Models.File file = _files.GetFile(id); Models.File file = _files.GetFile(id);
if (file != null && file.Folder.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, EntityNames.Folder, file.Folder.FolderId, PermissionNames.Edit)) if (file != null && file.Folder.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, file.Folder.SiteId, EntityNames.Folder, file.Folder.FolderId, PermissionNames.Edit))
{ {
string filepath = _files.GetFilePath(file); string filepath = _files.GetFilePath(file);
if (System.IO.File.Exists(filepath)) if (System.IO.File.Exists(filepath))
@ -328,7 +339,15 @@ namespace Oqtane.Controllers
var file = CreateFile(upload, FolderId, Path.Combine(folderPath, upload)); var file = CreateFile(upload, FolderId, Path.Combine(folderPath, upload));
if (file != null) if (file != null)
{ {
file = _files.AddFile(file); if (file.FileId == 0)
{
file = _files.AddFile(file);
}
else
{
file = _files.UpdateFile(file);
}
_logger.Log(LogLevel.Information, this, LogFunction.Create, "File Upload Succeeded {File}", Path.Combine(folderPath, upload));
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.File, file.FileId, SyncEventActions.Create); _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.File, file.FileId, SyncEventActions.Create);
} }
} }
@ -350,16 +369,16 @@ namespace Oqtane.Controllers
int totalparts = int.Parse(parts?.Substring(parts.IndexOf("_") + 1)); int totalparts = int.Parse(parts?.Substring(parts.IndexOf("_") + 1));
filename = Path.GetFileNameWithoutExtension(filename); // base filename filename = Path.GetFileNameWithoutExtension(filename); // base filename
string[] fileParts = Directory.GetFiles(folder, filename + token + "*"); // list of all file parts string[] fileparts = Directory.GetFiles(folder, filename + token + "*"); // list of all file parts
// if all of the file parts exist ( note that file parts can arrive out of order ) // if all of the file parts exist ( note that file parts can arrive out of order )
if (fileParts.Length == totalparts && CanAccessFiles(fileParts)) if (fileparts.Length == totalparts && CanAccessFiles(fileparts))
{ {
// merge file parts // merge file parts into temp file ( in case another user is trying to get the file )
bool success = true; bool success = true;
using (var stream = new FileStream(Path.Combine(folder, filename + ".tmp"), FileMode.Create)) using (var stream = new FileStream(Path.Combine(folder, filename + ".tmp"), FileMode.Create))
{ {
foreach (string filepart in fileParts) foreach (string filepart in fileparts)
{ {
try try
{ {
@ -375,52 +394,49 @@ namespace Oqtane.Controllers
} }
} }
// delete file parts and rename file // clean up file parts
foreach (var file in Directory.GetFiles(folder, "*" + token + "*"))
{
// file name matches part or is more than 2 hours old (ie. a prior file upload failed)
if (fileparts.Contains(file) || System.IO.File.GetCreationTime(file).ToUniversalTime() < DateTime.UtcNow.AddHours(-2))
{
System.IO.File.Delete(file);
}
}
// rename temp file
if (success) if (success)
{ {
foreach (string filepart in fileParts) // remove file if it already exists (as well as any thumbnails)
foreach (var file in Directory.GetFiles(folder, Path.GetFileNameWithoutExtension(filename) + ".*"))
{ {
System.IO.File.Delete(filepart); if (Path.GetExtension(file) != ".tmp")
{
System.IO.File.Delete(file);
}
} }
// remove file if it already exists // rename temp file now that the entire process is completed
if (System.IO.File.Exists(Path.Combine(folder, filename)))
{
System.IO.File.Delete(Path.Combine(folder, filename));
}
// rename file now that the entire process is completed
System.IO.File.Move(Path.Combine(folder, filename + ".tmp"), Path.Combine(folder, filename)); System.IO.File.Move(Path.Combine(folder, filename + ".tmp"), Path.Combine(folder, filename));
_logger.Log(LogLevel.Information, this, LogFunction.Create, "File Uploaded {File}", Path.Combine(folder, filename));
// return filename
merged = filename; merged = filename;
} }
} }
// clean up file parts which are more than 2 hours old ( which can happen if a prior file upload failed )
var cleanupFiles = Directory.EnumerateFiles(folder, "*" + token + "*")
.Where(f => Path.GetExtension(f).StartsWith(token) && !Path.GetFileName(f).StartsWith(filename));
foreach (var file in cleanupFiles)
{
var createdDate = System.IO.File.GetCreationTime(file).ToUniversalTime();
if (createdDate < DateTime.UtcNow.AddHours(-2))
{
System.IO.File.Delete(file);
}
}
return merged; return merged;
} }
private bool CanAccessFiles(string[] files) private bool CanAccessFiles(string[] files)
{ {
// ensure files are not locked by another process ( ie. still being written to ) // ensure files are not locked by another process
bool canaccess = true;
FileStream stream = null; FileStream stream = null;
bool locked = false;
foreach (string file in files) foreach (string file in files)
{ {
locked = true;
int attempts = 0; int attempts = 0;
bool locked = true; // note that this will wait a maximum of 15 seconds for a file to become unlocked
while (attempts < 5 && locked) while (attempts < 5 && locked)
{ {
try try
@ -430,7 +446,8 @@ namespace Oqtane.Controllers
} }
catch // file is locked by another process catch // file is locked by another process
{ {
Thread.Sleep(1000); // wait 1 second attempts += 1;
Thread.Sleep(1000 * attempts); // progressive retry
} }
finally finally
{ {
@ -439,20 +456,15 @@ namespace Oqtane.Controllers
stream.Close(); stream.Close();
} }
} }
attempts += 1;
} }
if (locked)
if (locked && canaccess)
{ {
canaccess = false; break; // file still locked after retrying
} }
} }
return !locked;
return canaccess;
} }
/// <summary> /// <summary>
/// Get file with header /// Get file with header
/// Content-Disposition: inline /// Content-Disposition: inline
@ -644,7 +656,7 @@ namespace Oqtane.Controllers
private Models.File CreateFile(string filename, int folderid, string filepath) private Models.File CreateFile(string filename, int folderid, string filepath)
{ {
Models.File file = null; var file = _files.GetFile(folderid, filename);
int size = 0; int size = 0;
var folder = _folders.GetFolder(folderid); var folder = _folders.GetFolder(folderid);
@ -659,7 +671,10 @@ namespace Oqtane.Controllers
FileInfo fileinfo = new FileInfo(filepath); FileInfo fileinfo = new FileInfo(filepath);
if (folder.Capacity == 0 || ((size + fileinfo.Length) / 1000000) < folder.Capacity) if (folder.Capacity == 0 || ((size + fileinfo.Length) / 1000000) < folder.Capacity)
{ {
file = new Models.File(); if (file == null)
{
file = new Models.File();
}
file.Name = filename; file.Name = filename;
file.FolderId = folderid; file.FolderId = folderid;
@ -689,7 +704,11 @@ namespace Oqtane.Controllers
else else
{ {
System.IO.File.Delete(filepath); System.IO.File.Delete(filepath);
_logger.Log(LogLevel.Warning, this, LogFunction.Create, "File Exceeds Folder Capacity {Folder} {File}", folder, filepath); if (file != null)
{
_files.DeleteFile(file.FileId);
}
_logger.Log(LogLevel.Warning, this, LogFunction.Create, "File Exceeds Folder Capacity And Has Been Removed {Folder} {File}", folder, filepath);
} }
return file; return file;

View File

@ -157,7 +157,7 @@ namespace Oqtane.Controllers
[Authorize(Roles = RoleNames.Registered)] [Authorize(Roles = RoleNames.Registered)]
public Folder Put(int id, [FromBody] Folder folder) public Folder Put(int id, [FromBody] Folder folder)
{ {
if (ModelState.IsValid && folder.SiteId == _alias.SiteId && _folders.GetFolder(folder.FolderId, false) != null && _userPermissions.IsAuthorized(User, EntityNames.Folder, folder.FolderId, PermissionNames.Edit)) if (ModelState.IsValid && folder.SiteId == _alias.SiteId && _folders.GetFolder(folder.FolderId, false) != null && _userPermissions.IsAuthorized(User, folder.SiteId, EntityNames.Folder, folder.FolderId, PermissionNames.Edit))
{ {
if (folder.IsPathValid()) if (folder.IsPathValid())
{ {
@ -199,7 +199,7 @@ namespace Oqtane.Controllers
[Authorize(Roles = RoleNames.Registered)] [Authorize(Roles = RoleNames.Registered)]
public void Put(int siteid, int folderid, int? parentid) public void Put(int siteid, int folderid, int? parentid)
{ {
if (siteid == _alias.SiteId && _folders.GetFolder(folderid, false) != null && _userPermissions.IsAuthorized(User, EntityNames.Folder, folderid, PermissionNames.Edit)) if (siteid == _alias.SiteId && _folders.GetFolder(folderid, false) != null && _userPermissions.IsAuthorized(User, siteid, EntityNames.Folder, folderid, PermissionNames.Edit))
{ {
int order = 1; int order = 1;
List<Folder> folders = _folders.GetFolders(siteid).ToList(); List<Folder> folders = _folders.GetFolders(siteid).ToList();
@ -228,7 +228,7 @@ namespace Oqtane.Controllers
public void Delete(int id) public void Delete(int id)
{ {
var folder = _folders.GetFolder(id, false); var folder = _folders.GetFolder(id, false);
if (folder != null && folder.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, EntityNames.Folder, id, PermissionNames.Edit)) if (folder != null && folder.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, folder.SiteId, EntityNames.Folder, id, PermissionNames.Edit))
{ {
if (Directory.Exists(_folders.GetFolderPath(folder))) if (Directory.Exists(_folders.GetFolderPath(folder)))
{ {

View File

@ -47,7 +47,7 @@ namespace Oqtane.Controllers
var code = Path.GetFileName(Path.GetDirectoryName(file)); var code = Path.GetFileName(Path.GetDirectoryName(file));
if (!languages.Any(item => item.Code == code)) if (!languages.Any(item => item.Code == code))
{ {
languages.Add(new Language { Code = code, Name = CultureInfo.GetCultureInfo(code).DisplayName, Version = FileVersionInfo.GetVersionInfo(file).FileVersion, IsDefault = false }); languages.Add(new Language { Code = code, Name = CultureInfo.GetCultureInfo(code).DisplayName, Version = FileVersionInfo.GetVersionInfo(file).ProductVersion, IsDefault = false });
} }
} }
} }
@ -62,7 +62,7 @@ namespace Oqtane.Controllers
var code = Path.GetFileName(Path.GetDirectoryName(file)); var code = Path.GetFileName(Path.GetDirectoryName(file));
if (languages.Any(item => item.Code == code)) if (languages.Any(item => item.Code == code))
{ {
languages.Single(item => item.Code == code).Version = FileVersionInfo.GetVersionInfo(file).FileVersion; languages.Single(item => item.Code == code).Version = FileVersionInfo.GetVersionInfo(file).ProductVersion;
} }
} }
} }

View File

@ -21,9 +21,18 @@ namespace Oqtane.Controllers
// GET: api/localization // GET: api/localization
[HttpGet()] [HttpGet()]
public IEnumerable<Culture> Get() public IEnumerable<Culture> Get(bool installed)
{ {
var cultures = _localizationManager.GetSupportedCultures().Select(c => new Culture string[] culturecodes;
if (installed)
{
culturecodes = _localizationManager.GetInstalledCultures();
}
else
{
culturecodes = _localizationManager.GetSupportedCultures();
}
var cultures = culturecodes.Select(c => new Culture
{ {
Name = CultureInfo.GetCultureInfo(c).Name, Name = CultureInfo.GetCultureInfo(c).Name,
DisplayName = CultureInfo.GetCultureInfo(c).DisplayName, DisplayName = CultureInfo.GetCultureInfo(c).DisplayName,

View File

@ -47,7 +47,6 @@ namespace Oqtane.Controllers
int SiteId; int SiteId;
if (int.TryParse(siteid, out SiteId) && SiteId == _alias.SiteId) if (int.TryParse(siteid, out SiteId) && SiteId == _alias.SiteId)
{ {
List<ModuleDefinition> moduledefinitions = _moduleDefinitions.GetModuleDefinitions(SiteId).ToList();
List<Setting> settings = _settings.GetSettings(EntityNames.Module).ToList(); List<Setting> settings = _settings.GetSettings(EntityNames.Module).ToList();
foreach (PageModule pagemodule in _pageModules.GetPageModules(SiteId)) foreach (PageModule pagemodule in _pageModules.GetPageModules(SiteId))
@ -75,7 +74,6 @@ namespace Oqtane.Controllers
module.Order = pagemodule.Order; module.Order = pagemodule.Order;
module.ContainerType = pagemodule.ContainerType; module.ContainerType = pagemodule.ContainerType;
module.ModuleDefinition = moduledefinitions.Find(item => item.ModuleDefinitionName == module.ModuleDefinitionName);
module.Settings = settings.Where(item => item.EntityId == pagemodule.ModuleId) module.Settings = settings.Where(item => item.EntityId == pagemodule.ModuleId)
.Where(item => !item.IsPrivate || _userPermissions.IsAuthorized(User, PermissionNames.Edit, pagemodule.Module.Permissions)) .Where(item => !item.IsPrivate || _userPermissions.IsAuthorized(User, PermissionNames.Edit, pagemodule.Module.Permissions))
.ToDictionary(setting => setting.SettingName, setting => setting.SettingValue); .ToDictionary(setting => setting.SettingName, setting => setting.SettingValue);
@ -121,7 +119,7 @@ namespace Oqtane.Controllers
[Authorize(Roles = RoleNames.Registered)] [Authorize(Roles = RoleNames.Registered)]
public Module Post([FromBody] Module module) public Module Post([FromBody] Module module)
{ {
if (ModelState.IsValid && module.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, EntityNames.Page, module.PageId, PermissionNames.Edit)) if (ModelState.IsValid && module.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, module.SiteId, EntityNames.Page, module.PageId, PermissionNames.Edit))
{ {
module = _modules.AddModule(module); module = _modules.AddModule(module);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Module, module.ModuleId, SyncEventActions.Create); _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Module, module.ModuleId, SyncEventActions.Create);
@ -144,7 +142,7 @@ namespace Oqtane.Controllers
{ {
var _module = _modules.GetModule(module.ModuleId, false); var _module = _modules.GetModule(module.ModuleId, false);
if (ModelState.IsValid && module.SiteId == _alias.SiteId && _module != null && _userPermissions.IsAuthorized(User, EntityNames.Module, module.ModuleId, PermissionNames.Edit)) if (ModelState.IsValid && module.SiteId == _alias.SiteId && _module != null && _userPermissions.IsAuthorized(User, module.SiteId, EntityNames.Module, module.ModuleId, PermissionNames.Edit))
{ {
module = _modules.UpdateModule(module); module = _modules.UpdateModule(module);
@ -194,7 +192,7 @@ namespace Oqtane.Controllers
public void Delete(int id) public void Delete(int id)
{ {
var module = _modules.GetModule(id); var module = _modules.GetModule(id);
if (module != null && module.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, EntityNames.Module, module.ModuleId, PermissionNames.Edit)) if (module != null && module.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, module.SiteId, EntityNames.Module, module.ModuleId, PermissionNames.Edit))
{ {
_modules.DeleteModule(id); _modules.DeleteModule(id);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Module, module.ModuleId, SyncEventActions.Delete); _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Module, module.ModuleId, SyncEventActions.Delete);
@ -215,7 +213,7 @@ namespace Oqtane.Controllers
{ {
string content = ""; string content = "";
var module = _modules.GetModule(moduleid); var module = _modules.GetModule(moduleid);
if (module != null && module.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, EntityNames.Page, pageid, PermissionNames.Edit)) if (module != null && module.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, module.SiteId, EntityNames.Page, pageid, PermissionNames.Edit))
{ {
content = _modules.ExportModule(moduleid); content = _modules.ExportModule(moduleid);
if (!string.IsNullOrEmpty(content)) if (!string.IsNullOrEmpty(content))
@ -242,7 +240,7 @@ namespace Oqtane.Controllers
{ {
bool success = false; bool success = false;
var module = _modules.GetModule(moduleid); var module = _modules.GetModule(moduleid);
if (ModelState.IsValid && module != null && module.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, EntityNames.Page, pageid, PermissionNames.Edit)) if (ModelState.IsValid && module != null && module.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, module.SiteId, EntityNames.Page, pageid, PermissionNames.Edit))
{ {
success = _modules.ImportModule(moduleid, content); success = _modules.ImportModule(moduleid, content);
if (success) if (success)

View File

@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Http;
using Oqtane.Infrastructure; using Oqtane.Infrastructure;
using System.Collections.Generic; using System.Collections.Generic;
using System; using System;
using Oqtane.Shared;
namespace Oqtane.Controllers namespace Oqtane.Controllers
{ {
@ -48,5 +49,9 @@ namespace Oqtane.Controllers
} }
} }
protected bool IsAuthorizedEntityId(string entityname, int entityid)
{
return (entityid == AuthEntityId(entityname)) || User.IsInRole(RoleNames.Host);
}
} }
} }

View File

@ -253,10 +253,10 @@ namespace Oqtane.Controllers
// get current page // get current page
var currentPage = _pages.GetPage(page.PageId, false); var currentPage = _pages.GetPage(page.PageId, false);
if (ModelState.IsValid && page.SiteId == _alias.SiteId && currentPage != null && _userPermissions.IsAuthorized(User, EntityNames.Page, page.PageId, PermissionNames.Edit)) if (ModelState.IsValid && page.SiteId == _alias.SiteId && currentPage != null && _userPermissions.IsAuthorized(User, page.SiteId, EntityNames.Page, page.PageId, PermissionNames.Edit))
{ {
// get current page permissions // get current page permissions
var currentPermissions = _permissionRepository.GetPermissions(EntityNames.Page, page.PageId).ToList(); var currentPermissions = _permissionRepository.GetPermissions(page.SiteId, EntityNames.Page, page.PageId).ToList();
page = _pages.UpdatePage(page); page = _pages.UpdatePage(page);
@ -281,9 +281,9 @@ namespace Oqtane.Controllers
// synchronize module permissions // synchronize module permissions
if (added.Count > 0 || removed.Count > 0) if (added.Count > 0 || removed.Count > 0)
{ {
foreach (PageModule pageModule in _pageModules.GetPageModules(page.PageId, "").ToList()) foreach (PageModule pageModule in _pageModules.GetPageModules(page.SiteId).Where(item => item.PageId == page.PageId).ToList())
{ {
var modulePermissions = _permissionRepository.GetPermissions(EntityNames.Module, pageModule.Module.ModuleId).ToList(); var modulePermissions = _permissionRepository.GetPermissions(pageModule.Module.SiteId, EntityNames.Module, pageModule.Module.ModuleId).ToList();
// permissions added // permissions added
foreach(Permission permission in added) foreach(Permission permission in added)
{ {
@ -346,7 +346,7 @@ namespace Oqtane.Controllers
[Authorize(Roles = RoleNames.Registered)] [Authorize(Roles = RoleNames.Registered)]
public void Put(int siteid, int pageid, int? parentid) public void Put(int siteid, int pageid, int? parentid)
{ {
if (siteid == _alias.SiteId && siteid == _alias.SiteId && _pages.GetPage(pageid, false) != null && _userPermissions.IsAuthorized(User, EntityNames.Page, pageid, PermissionNames.Edit)) if (siteid == _alias.SiteId && siteid == _alias.SiteId && _pages.GetPage(pageid, false) != null && _userPermissions.IsAuthorized(User, siteid, EntityNames.Page, pageid, PermissionNames.Edit))
{ {
int order = 1; int order = 1;
List<Page> pages = _pages.GetPages(siteid).ToList(); List<Page> pages = _pages.GetPages(siteid).ToList();
@ -377,7 +377,7 @@ namespace Oqtane.Controllers
public void Delete(int id) public void Delete(int id)
{ {
Page page = _pages.GetPage(id); Page page = _pages.GetPage(id);
if (page != null && page.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, EntityNames.Page, page.PageId, PermissionNames.Edit)) if (page != null && page.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, page.SiteId, EntityNames.Page, page.PageId, PermissionNames.Edit))
{ {
_pages.DeletePage(page.PageId); _pages.DeletePage(page.PageId);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Page, page.PageId, SyncEventActions.Delete); _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.Page, page.PageId, SyncEventActions.Delete);

View File

@ -73,7 +73,7 @@ namespace Oqtane.Controllers
public PageModule Post([FromBody] PageModule pageModule) public PageModule Post([FromBody] PageModule pageModule)
{ {
var page = _pages.GetPage(pageModule.PageId); var page = _pages.GetPage(pageModule.PageId);
if (ModelState.IsValid && page != null && page.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, EntityNames.Page, pageModule.PageId, PermissionNames.Edit)) if (ModelState.IsValid && page != null && page.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, page.SiteId, EntityNames.Page, pageModule.PageId, PermissionNames.Edit))
{ {
pageModule = _pageModules.AddPageModule(pageModule); pageModule = _pageModules.AddPageModule(pageModule);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.PageModule, pageModule.PageModuleId, SyncEventActions.Create); _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.PageModule, pageModule.PageModuleId, SyncEventActions.Create);
@ -95,7 +95,7 @@ namespace Oqtane.Controllers
public PageModule Put(int id, [FromBody] PageModule pageModule) public PageModule Put(int id, [FromBody] PageModule pageModule)
{ {
var page = _pages.GetPage(pageModule.PageId); var page = _pages.GetPage(pageModule.PageId);
if (ModelState.IsValid && page != null && page.SiteId == _alias.SiteId && _pageModules.GetPageModule(pageModule.PageModuleId, false) != null && _userPermissions.IsAuthorized(User, EntityNames.Page, pageModule.PageId, PermissionNames.Edit)) if (ModelState.IsValid && page != null && page.SiteId == _alias.SiteId && _pageModules.GetPageModule(pageModule.PageModuleId, false) != null && _userPermissions.IsAuthorized(User, page.SiteId, EntityNames.Page, pageModule.PageId, PermissionNames.Edit))
{ {
pageModule = _pageModules.UpdatePageModule(pageModule); pageModule = _pageModules.UpdatePageModule(pageModule);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.PageModule, pageModule.PageModuleId, SyncEventActions.Update); _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.PageModule, pageModule.PageModuleId, SyncEventActions.Update);
@ -117,10 +117,11 @@ namespace Oqtane.Controllers
public void Put(int pageid, string pane) public void Put(int pageid, string pane)
{ {
var page = _pages.GetPage(pageid); var page = _pages.GetPage(pageid);
if (page != null && page.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, EntityNames.Page, pageid, PermissionNames.Edit)) if (page != null && page.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, page.SiteId, EntityNames.Page, pageid, PermissionNames.Edit))
{ {
int order = 1; int order = 1;
List<PageModule> pagemodules = _pageModules.GetPageModules(pageid, pane).OrderBy(item => item.Order).ToList(); List<PageModule> pagemodules = _pageModules.GetPageModules(page.SiteId)
.Where(item => item.PageId == pageid && item.Pane == pane).OrderBy(item => item.Order).ToList();
foreach (PageModule pagemodule in pagemodules) foreach (PageModule pagemodule in pagemodules)
{ {
if (pagemodule.Order != order) if (pagemodule.Order != order)
@ -147,7 +148,7 @@ namespace Oqtane.Controllers
public void Delete(int id) public void Delete(int id)
{ {
PageModule pagemodule = _pageModules.GetPageModule(id); PageModule pagemodule = _pageModules.GetPageModule(id);
if (pagemodule != null && pagemodule.Module.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, EntityNames.Page, pagemodule.PageId, PermissionNames.Edit)) if (pagemodule != null && pagemodule.Module.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, pagemodule.Module.SiteId, EntityNames.Page, pagemodule.PageId, PermissionNames.Edit))
{ {
_pageModules.DeletePageModule(id); _pageModules.DeletePageModule(id);
_syncManager.AddSyncEvent(_alias.TenantId, EntityNames.PageModule, pagemodule.PageModuleId, SyncEventActions.Delete); _syncManager.AddSyncEvent(_alias.TenantId, EntityNames.PageModule, pagemodule.PageModuleId, SyncEventActions.Delete);

View File

@ -24,10 +24,11 @@ namespace Oqtane.Controllers
_syncManager = syncManager; _syncManager = syncManager;
_logger = logger; _logger = logger;
_alias = tenantManager.GetAlias(); _alias = tenantManager.GetAlias();
} }
// GET: api/<controller>?siteid=x // GET: api/<controller>?siteid=x
[HttpGet] [HttpGet]
[Authorize(Roles = RoleNames.Registered)]
public IEnumerable<Profile> Get(string siteid) public IEnumerable<Profile> Get(string siteid)
{ {
int SiteId; int SiteId;
@ -45,6 +46,7 @@ namespace Oqtane.Controllers
// GET api/<controller>/5 // GET api/<controller>/5
[HttpGet("{id}")] [HttpGet("{id}")]
[Authorize(Roles = RoleNames.Registered)]
public Profile Get(int id) public Profile Get(int id)
{ {
var profile = _profiles.GetProfile(id); var profile = _profiles.GetProfile(id);
@ -62,7 +64,7 @@ namespace Oqtane.Controllers
// POST api/<controller> // POST api/<controller>
[HttpPost] [HttpPost]
[Authorize(Roles = RoleNames.Admin)] [Authorize(Policy = $"{EntityNames.Profile}:{PermissionNames.Write}:{RoleNames.Admin}")]
public Profile Post([FromBody] Profile profile) public Profile Post([FromBody] Profile profile)
{ {
if (ModelState.IsValid && profile.SiteId == _alias.SiteId) if (ModelState.IsValid && profile.SiteId == _alias.SiteId)
@ -82,7 +84,7 @@ namespace Oqtane.Controllers
// PUT api/<controller>/5 // PUT api/<controller>/5
[HttpPut("{id}")] [HttpPut("{id}")]
[Authorize(Roles = RoleNames.Admin)] [Authorize(Policy = $"{EntityNames.Profile}:{PermissionNames.Write}:{RoleNames.Admin}")]
public Profile Put(int id, [FromBody] Profile profile) public Profile Put(int id, [FromBody] Profile profile)
{ {
if (ModelState.IsValid && profile.SiteId == _alias.SiteId && _profiles.GetProfile(profile.ProfileId, false) != null) if (ModelState.IsValid && profile.SiteId == _alias.SiteId && _profiles.GetProfile(profile.ProfileId, false) != null)
@ -102,7 +104,7 @@ namespace Oqtane.Controllers
// DELETE api/<controller>/5 // DELETE api/<controller>/5
[HttpDelete("{id}")] [HttpDelete("{id}")]
[Authorize(Roles = RoleNames.Admin)] [Authorize(Policy = $"{EntityNames.Profile}:{PermissionNames.Write}:{RoleNames.Admin}")]
public void Delete(int id) public void Delete(int id)
{ {
var profile = _profiles.GetProfile(id); var profile = _profiles.GetProfile(id);

View File

@ -68,7 +68,7 @@ namespace Oqtane.Controllers
// POST api/<controller> // POST api/<controller>
[HttpPost] [HttpPost]
[Authorize(Roles = RoleNames.Admin)] [Authorize(Policy = $"{EntityNames.Role}:{PermissionNames.Write}:{RoleNames.Admin}")]
public Role Post([FromBody] Role role) public Role Post([FromBody] Role role)
{ {
if (ModelState.IsValid && role.SiteId == _alias.SiteId) if (ModelState.IsValid && role.SiteId == _alias.SiteId)
@ -88,7 +88,7 @@ namespace Oqtane.Controllers
// PUT api/<controller>/5 // PUT api/<controller>/5
[HttpPut("{id}")] [HttpPut("{id}")]
[Authorize(Roles = RoleNames.Admin)] [Authorize(Policy = $"{EntityNames.Role}:{PermissionNames.Write}:{RoleNames.Admin}")]
public Role Put(int id, [FromBody] Role role) public Role Put(int id, [FromBody] Role role)
{ {
if (ModelState.IsValid && role.SiteId == _alias.SiteId && _roles.GetRole(role.RoleId, false) != null) if (ModelState.IsValid && role.SiteId == _alias.SiteId && _roles.GetRole(role.RoleId, false) != null)
@ -108,7 +108,7 @@ namespace Oqtane.Controllers
// DELETE api/<controller>/5 // DELETE api/<controller>/5
[HttpDelete("{id}")] [HttpDelete("{id}")]
[Authorize(Roles = RoleNames.Admin)] [Authorize(Policy = $"{EntityNames.Role}:{PermissionNames.Write}:{RoleNames.Admin}")]
public void Delete(int id) public void Delete(int id)
{ {
var role = _roles.GetRole(id); var role = _roles.GetRole(id);

View File

@ -206,13 +206,13 @@ namespace Oqtane.Controllers
case EntityNames.Page: case EntityNames.Page:
case EntityNames.Module: case EntityNames.Module:
case EntityNames.Folder: case EntityNames.Folder:
authorized = _userPermissions.IsAuthorized(User, entityName, entityId, permissionName); authorized = _userPermissions.IsAuthorized(User, _alias.SiteId, entityName, entityId, permissionName);
break; break;
case EntityNames.User: case EntityNames.User:
authorized = true; authorized = true;
if (permissionName == PermissionNames.Edit) if (permissionName == PermissionNames.Edit)
{ {
authorized = User.IsInRole(RoleNames.Admin) || (_userPermissions.GetUser(User).UserId == entityId); authorized = _userPermissions.IsAuthorized(User, _alias.SiteId, entityName, -1, PermissionNames.Write, RoleNames.Admin) || (_userPermissions.GetUser(User).UserId == entityId);
} }
break; break;
case EntityNames.Visitor: case EntityNames.Visitor:
@ -226,13 +226,11 @@ namespace Oqtane.Controllers
} }
break; break;
default: // custom entity default: // custom entity
authorized = true;
if (permissionName == PermissionNames.Edit) if (permissionName == PermissionNames.Edit)
{ {
authorized = User.IsInRole(RoleNames.Admin) || _userPermissions.IsAuthorized(User, entityName, entityId, permissionName); authorized = _userPermissions.IsAuthorized(User, _alias.SiteId, entityName, entityId, permissionName) ||
} _userPermissions.IsAuthorized(User, _alias.SiteId, entityName, -1, PermissionNames.Write, RoleNames.Admin);
else
{
authorized = true;
} }
break; break;
} }
@ -255,7 +253,7 @@ namespace Oqtane.Controllers
case EntityNames.Page: case EntityNames.Page:
case EntityNames.Module: case EntityNames.Module:
case EntityNames.Folder: case EntityNames.Folder:
filter = !_userPermissions.IsAuthorized(User, entityName, entityId, PermissionNames.Edit); filter = !_userPermissions.IsAuthorized(User, _alias.SiteId, entityName, entityId, PermissionNames.Edit);
break; break;
case EntityNames.User: case EntityNames.User:
filter = !User.IsInRole(RoleNames.Admin) && _userPermissions.GetUser(User).UserId != entityId; filter = !User.IsInRole(RoleNames.Admin) && _userPermissions.GetUser(User).UserId != entityId;
@ -271,7 +269,7 @@ namespace Oqtane.Controllers
} }
break; break;
default: // custom entity default: // custom entity
filter = !User.IsInRole(RoleNames.Admin) && !_userPermissions.IsAuthorized(User, entityName, entityId, PermissionNames.Edit); filter = !User.IsInRole(RoleNames.Admin) && !_userPermissions.IsAuthorized(User, _alias.SiteId, entityName, entityId, PermissionNames.Edit);
break; break;
} }
return filter; return filter;

View File

@ -53,7 +53,7 @@ namespace Oqtane.Controllers
catch (Exception ex) catch (Exception ex)
{ {
results.Add(new Dictionary<string, string>() { { "Error", ex.Message } }); results.Add(new Dictionary<string, string>() { { "Error", ex.Message } });
_logger.Log(LogLevel.Error, this, LogFunction.Other, ex, "Sql Query {Query} Executed on Tenant {TenantId} Resulted In An Error {Error}", sqlquery.Query, sqlquery.TenantId, ex.Message); _logger.Log(LogLevel.Warning, this, LogFunction.Other, "Sql Query {Query} Executed on Tenant {TenantId} Resulted In An Error {Error}", sqlquery.Query, sqlquery.TenantId, ex.Message);
} }
sqlquery.Results = results; sqlquery.Results = results;
return sqlquery; return sqlquery;

View File

@ -6,6 +6,7 @@ using System;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Oqtane.Infrastructure; using Oqtane.Infrastructure;
using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Features;
using System.IO;
namespace Oqtane.Controllers namespace Oqtane.Controllers
{ {
@ -53,6 +54,15 @@ namespace Oqtane.Controllers
systeminfo.Add("UseSwagger", _configManager.GetSetting("UseSwagger", "true")); systeminfo.Add("UseSwagger", _configManager.GetSetting("UseSwagger", "true"));
systeminfo.Add("PackageService", _configManager.GetSetting("PackageService", "true")); systeminfo.Add("PackageService", _configManager.GetSetting("PackageService", "true"));
break; break;
case "log":
string log = "";
string path = Path.Combine(_environment.ContentRootPath, "Content", "Log", "error.log");
if (System.IO.File.Exists(path))
{
log = System.IO.File.ReadAllText(path);
}
systeminfo.Add("Log", log);
break;
} }
return systeminfo; return systeminfo;

View File

@ -29,23 +29,25 @@ namespace Oqtane.Controllers
private readonly ITenantManager _tenantManager; private readonly ITenantManager _tenantManager;
private readonly INotificationRepository _notifications; private readonly INotificationRepository _notifications;
private readonly IFolderRepository _folders; private readonly IFolderRepository _folders;
private readonly ISyncManager _syncManager;
private readonly ISiteRepository _sites; private readonly ISiteRepository _sites;
private readonly IUserPermissions _userPermissions;
private readonly IJwtManager _jwtManager; private readonly IJwtManager _jwtManager;
private readonly ISyncManager _syncManager;
private readonly ILogManager _logger; private readonly ILogManager _logger;
public UserController(IUserRepository users, IUserRoleRepository userRoles, UserManager<IdentityUser> identityUserManager, SignInManager<IdentityUser> identitySignInManager, ITenantManager tenantManager, INotificationRepository notifications, IFolderRepository folders, ISyncManager syncManager, ISiteRepository sites, IJwtManager jwtManager, ILogManager logger) public UserController(IUserRepository users, IUserRoleRepository userRoles, UserManager<IdentityUser> identityUserManager, SignInManager<IdentityUser> identitySignInManager, ITenantManager tenantManager, INotificationRepository notifications, IFolderRepository folders, ISiteRepository sites, IUserPermissions userPermissions, IJwtManager jwtManager, ISyncManager syncManager, ILogManager logger)
{ {
_users = users; _users = users;
_userRoles = userRoles; _userRoles = userRoles;
_identityUserManager = identityUserManager; _identityUserManager = identityUserManager;
_identitySignInManager = identitySignInManager; _identitySignInManager = identitySignInManager;
_tenantManager = tenantManager; _tenantManager = tenantManager;
_folders = folders;
_notifications = notifications; _notifications = notifications;
_syncManager = syncManager; _folders = folders;
_sites = sites; _sites = sites;
_userPermissions = userPermissions;
_jwtManager = jwtManager; _jwtManager = jwtManager;
_syncManager = syncManager;
_logger = logger; _logger = logger;
} }
@ -105,7 +107,7 @@ namespace Oqtane.Controllers
user.TwoFactorCode = ""; user.TwoFactorCode = "";
user.TwoFactorExpiry = null; user.TwoFactorExpiry = null;
if (!User.IsInRole(RoleNames.Admin) && User.Identity.Name?.ToLower() != user.Username.ToLower()) if (!_userPermissions.IsAuthorized(User, user.SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin) && User.Identity.Name?.ToLower() != user.Username.ToLower())
{ {
user.Email = ""; user.Email = "";
user.PhotoFileId = null; user.PhotoFileId = null;
@ -149,7 +151,7 @@ namespace Oqtane.Controllers
bool verified; bool verified;
bool allowregistration; bool allowregistration;
if (User.IsInRole(RoleNames.Admin)) if (_userPermissions.IsAuthorized(User, user.SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin))
{ {
verified = true; verified = true;
allowregistration = true; allowregistration = true;
@ -241,23 +243,40 @@ namespace Oqtane.Controllers
[Authorize] [Authorize]
public async Task<User> Put(int id, [FromBody] User user) public async Task<User> Put(int id, [FromBody] User user)
{ {
if (ModelState.IsValid && user.SiteId == _tenantManager.GetAlias().SiteId && _users.GetUser(user.UserId, false) != null && (User.IsInRole(RoleNames.Admin) || User.Identity.Name == user.Username)) if (ModelState.IsValid && user.SiteId == _tenantManager.GetAlias().SiteId && _users.GetUser(user.UserId, false) != null
&& (_userPermissions.IsAuthorized(User, user.SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin) || User.Identity.Name == user.Username))
{ {
IdentityUser identityuser = await _identityUserManager.FindByNameAsync(user.Username); IdentityUser identityuser = await _identityUserManager.FindByNameAsync(user.Username);
if (identityuser != null) if (identityuser != null)
{ {
identityuser.Email = user.Email; identityuser.Email = user.Email;
var valid = true;
if (user.Password != "") if (user.Password != "")
{ {
identityuser.PasswordHash = _identityUserManager.PasswordHasher.HashPassword(identityuser, user.Password); var validator = new PasswordValidator<IdentityUser>();
var result = await validator.ValidateAsync(_identityUserManager, null, user.Password);
valid = result.Succeeded;
if (valid)
{
identityuser.PasswordHash = _identityUserManager.PasswordHasher.HashPassword(identityuser, user.Password);
}
}
if (valid)
{
await _identityUserManager.UpdateAsync(identityuser);
user = _users.UpdateUser(user);
_syncManager.AddSyncEvent(_tenantManager.GetAlias().TenantId, EntityNames.User, user.UserId, SyncEventActions.Update);
_syncManager.AddSyncEvent(_tenantManager.GetAlias().TenantId, EntityNames.User, user.UserId, SyncEventActions.Refresh);
user.Password = ""; // remove sensitive information
_logger.Log(LogLevel.Information, this, LogFunction.Update, "User Updated {User}", user);
}
else
{
_logger.Log(user.SiteId, LogLevel.Error, this, LogFunction.Update, "Unable To Update User {Username}. Password Does Not Meet Complexity Requirements.", user.Username);
user = null;
} }
await _identityUserManager.UpdateAsync(identityuser);
} }
user = _users.UpdateUser(user);
_syncManager.AddSyncEvent(_tenantManager.GetAlias().TenantId, EntityNames.User, user.UserId, SyncEventActions.Update);
_syncManager.AddSyncEvent(_tenantManager.GetAlias().TenantId, EntityNames.User, user.UserId, SyncEventActions.Refresh);
user.Password = ""; // remove sensitive information
_logger.Log(LogLevel.Information, this, LogFunction.Update, "User Updated {User}", user);
} }
else else
{ {
@ -271,7 +290,7 @@ namespace Oqtane.Controllers
// DELETE api/<controller>/5?siteid=x // DELETE api/<controller>/5?siteid=x
[HttpDelete("{id}")] [HttpDelete("{id}")]
[Authorize(Roles = RoleNames.Admin)] [Authorize(Policy = $"{EntityNames.User}:{PermissionNames.Write}:{RoleNames.Admin}")]
public async Task Delete(int id, string siteid) public async Task Delete(int id, string siteid)
{ {
int SiteId; int SiteId;

View File

@ -10,6 +10,7 @@ using System.Linq;
using System.Net; using System.Net;
using Oqtane.Security; using Oqtane.Security;
using System; using System;
using Oqtane.Modules.Admin.Roles;
namespace Oqtane.Controllers namespace Oqtane.Controllers
{ {
@ -93,7 +94,7 @@ namespace Oqtane.Controllers
userrole.User.TwoFactorCode = ""; userrole.User.TwoFactorCode = "";
userrole.User.TwoFactorExpiry = null; userrole.User.TwoFactorExpiry = null;
if (!User.IsInRole(RoleNames.Admin) && userid != userrole.User.UserId) if (!_userPermissions.IsAuthorized(User, userrole.User.SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin) && userid != userrole.User.UserId)
{ {
userrole.User.Email = ""; userrole.User.Email = "";
userrole.User.PhotoFileId = null; userrole.User.PhotoFileId = null;
@ -115,7 +116,7 @@ namespace Oqtane.Controllers
// POST api/<controller> // POST api/<controller>
[HttpPost] [HttpPost]
[Authorize(Roles = RoleNames.Admin)] [Authorize(Policy = $"{EntityNames.UserRole}:{PermissionNames.Write}:{RoleNames.Admin}")]
public UserRole Post([FromBody] UserRole userRole) public UserRole Post([FromBody] UserRole userRole)
{ {
var role = _roles.GetRole(userRole.RoleId); var role = _roles.GetRole(userRole.RoleId);
@ -138,7 +139,7 @@ namespace Oqtane.Controllers
// PUT api/<controller>/5 // PUT api/<controller>/5
[HttpPut("{id}")] [HttpPut("{id}")]
[Authorize(Roles = RoleNames.Admin)] [Authorize(Policy = $"{EntityNames.UserRole}:{PermissionNames.Write}:{RoleNames.Admin}")]
public UserRole Put(int id, [FromBody] UserRole userRole) public UserRole Put(int id, [FromBody] UserRole userRole)
{ {
var role = _roles.GetRole(userRole.RoleId); var role = _roles.GetRole(userRole.RoleId);
@ -160,7 +161,7 @@ namespace Oqtane.Controllers
// DELETE api/<controller>/5 // DELETE api/<controller>/5
[HttpDelete("{id}")] [HttpDelete("{id}")]
[Authorize(Roles = RoleNames.Admin)] [Authorize(Policy = $"{EntityNames.UserRole}:{PermissionNames.Write}:{RoleNames.Admin}")]
public void Delete(int id) public void Delete(int id)
{ {
UserRole userrole = _userRoles.GetUserRole(id); UserRole userrole = _userRoles.GetUserRole(id);

View File

@ -6,11 +6,11 @@ using System.Linq;
using System.Net; using System.Net;
using System.Net.Http; using System.Net.Http;
using System.Reflection; using System.Reflection;
using System.Reflection.Metadata;
using System.Runtime.Loader; using System.Runtime.Loader;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication.OAuth; using Microsoft.AspNetCore.Authentication.OAuth;
using Microsoft.AspNetCore.Authentication.OpenIdConnect; using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
@ -47,22 +47,6 @@ namespace Microsoft.Extensions.DependencyInjection
return services; return services;
} }
public static IServiceCollection AddOqtaneAuthorizationPolicies(this IServiceCollection services)
{
services.AddAuthorizationCore(options =>
{
options.AddPolicy(PolicyNames.ViewPage, policy => policy.Requirements.Add(new PermissionRequirement(EntityNames.Page, PermissionNames.View)));
options.AddPolicy(PolicyNames.EditPage, policy => policy.Requirements.Add(new PermissionRequirement(EntityNames.Page, PermissionNames.Edit)));
options.AddPolicy(PolicyNames.ViewModule, policy => policy.Requirements.Add(new PermissionRequirement(EntityNames.Module, PermissionNames.View)));
options.AddPolicy(PolicyNames.EditModule, policy => policy.Requirements.Add(new PermissionRequirement(EntityNames.Module, PermissionNames.Edit)));
options.AddPolicy(PolicyNames.ViewFolder, policy => policy.Requirements.Add(new PermissionRequirement(EntityNames.Folder, PermissionNames.View)));
options.AddPolicy(PolicyNames.EditFolder, policy => policy.Requirements.Add(new PermissionRequirement(EntityNames.Folder, PermissionNames.Edit)));
options.AddPolicy(PolicyNames.ListFolder, policy => policy.Requirements.Add(new PermissionRequirement(EntityNames.Folder, PermissionNames.Browse)));
});
return services;
}
public static OqtaneSiteOptionsBuilder AddOqtaneSiteOptions(this IServiceCollection services) public static OqtaneSiteOptionsBuilder AddOqtaneSiteOptions(this IServiceCollection services)
{ {
return new OqtaneSiteOptionsBuilder(services); return new OqtaneSiteOptionsBuilder(services);
@ -76,6 +60,7 @@ namespace Microsoft.Extensions.DependencyInjection
services.AddSingleton<IConfigManager, ConfigManager>(); services.AddSingleton<IConfigManager, ConfigManager>();
services.AddSingleton<ILoggerProvider, FileLoggerProvider>(); services.AddSingleton<ILoggerProvider, FileLoggerProvider>();
services.AddSingleton<AutoValidateAntiforgeryTokenFilter>(); services.AddSingleton<AutoValidateAntiforgeryTokenFilter>();
services.AddSingleton<IAuthorizationPolicyProvider, AuthorizationPolicyProvider>();
return services; return services;
} }

View File

@ -19,6 +19,7 @@ using System.Net.Http.Headers;
using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.Cookies;
using System.Net; using System.Net;
using System.Text.Json.Nodes; using System.Text.Json.Nodes;
using System.Globalization;
namespace Oqtane.Extensions namespace Oqtane.Extensions
{ {
@ -327,7 +328,7 @@ namespace Oqtane.Extensions
identityuser.UserName = email; identityuser.UserName = email;
identityuser.Email = email; identityuser.Email = email;
identityuser.EmailConfirmed = true; identityuser.EmailConfirmed = true;
var result = await _identityUserManager.CreateAsync(identityuser, DateTime.UtcNow.ToString("yyyy-MMM-dd-HH-mm-ss")); var result = await _identityUserManager.CreateAsync(identityuser, DateTime.UtcNow.ToString("yyyy-MMM-dd-HH-mm-ss", CultureInfo.InvariantCulture));
if (result.Succeeded) if (result.Succeeded)
{ {
user = new User user = new User

View File

@ -1,4 +1,4 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Text.Json; using System.Text.Json;
@ -11,20 +11,22 @@ namespace Oqtane.Extensions
public static string EncodePermissions(this IEnumerable<Permission> permissionList) public static string EncodePermissions(this IEnumerable<Permission> permissionList)
{ {
List<PermissionString> permissionstrings = new List<PermissionString>(); List<PermissionString> permissionstrings = new List<PermissionString>();
string entityname = "";
string permissionname = ""; string permissionname = "";
string permissions = ""; string permissions = "";
StringBuilder permissionsbuilder = new StringBuilder(); StringBuilder permissionsbuilder = new StringBuilder();
string securityid = ""; string securityid = "";
foreach (Permission permission in permissionList.OrderBy(item => item.PermissionName)) foreach (Permission permission in permissionList.OrderBy(item => item.EntityName).ThenBy(item => item.PermissionName))
{ {
// permission collections are grouped by permissionname // permission collections are grouped by entityname and permissionname
if (permissionname != permission.PermissionName) if (entityname != permission.EntityName || permissionname != permission.PermissionName)
{ {
permissions = permissionsbuilder.ToString(); permissions = permissionsbuilder.ToString();
if (permissions != "") if (permissions != "")
{ {
permissionstrings.Add(new PermissionString { PermissionName = permissionname, Permissions = permissions.Substring(0, permissions.Length - 1) }); permissionstrings.Add(new PermissionString { EntityName = entityname, PermissionName = permissionname, Permissions = permissions.Substring(0, permissions.Length - 1) });
} }
entityname = permission.EntityName;
permissionname = permission.PermissionName; permissionname = permission.PermissionName;
permissionsbuilder = new StringBuilder(); permissionsbuilder = new StringBuilder();
} }
@ -56,7 +58,7 @@ namespace Oqtane.Extensions
permissions = permissionsbuilder.ToString(); permissions = permissionsbuilder.ToString();
if (permissions != "") if (permissions != "")
{ {
permissionstrings.Add(new PermissionString { PermissionName = permissionname, Permissions = permissions.Substring(0, permissions.Length - 1) }); permissionstrings.Add(new PermissionString { EntityName = entityname, PermissionName = permissionname, Permissions = permissions.Substring(0, permissions.Length - 1) });
} }
return JsonSerializer.Serialize(permissionstrings); return JsonSerializer.Serialize(permissionstrings);
} }

View File

@ -66,7 +66,7 @@ namespace Oqtane.Infrastructure
List<Notification> notifications = notificationRepository.GetNotifications(site.SiteId, -1, -1).ToList(); List<Notification> notifications = notificationRepository.GetNotifications(site.SiteId, -1, -1).ToList();
foreach (Notification notification in notifications) foreach (Notification notification in notifications)
{ {
// get sender and receiver information if not provided // get sender and receiver information from user object if not provided
if ((string.IsNullOrEmpty(notification.FromEmail) || string.IsNullOrEmpty(notification.FromDisplayName)) && notification.FromUserId != null) if ((string.IsNullOrEmpty(notification.FromEmail) || string.IsNullOrEmpty(notification.FromDisplayName)) && notification.FromUserId != null)
{ {
var user = userRepository.GetUser(notification.FromUserId.Value); var user = userRepository.GetUser(notification.FromUserId.Value);
@ -96,31 +96,41 @@ namespace Oqtane.Infrastructure
else else
{ {
MailMessage mailMessage = new MailMessage(); MailMessage mailMessage = new MailMessage();
mailMessage.From = new MailAddress(settings["SMTPSender"], site.Name);
mailMessage.Subject = notification.Subject; // sender
if (!string.IsNullOrEmpty(notification.FromEmail) && !string.IsNullOrEmpty(notification.FromDisplayName)) if (settings.ContainsKey("SMTPRelay") && settings["SMTPRelay"] == "True" && !string.IsNullOrEmpty(notification.FromEmail))
{ {
mailMessage.Body = "From: " + notification.FromDisplayName + "<" + notification.FromEmail + ">" + "\n"; if (!string.IsNullOrEmpty(notification.FromDisplayName))
{
mailMessage.From = new MailAddress(notification.FromEmail, notification.FromDisplayName);
}
else
{
mailMessage.From = new MailAddress(notification.FromEmail);
}
} }
else else
{ {
mailMessage.Body = "From: " + site.Name + "\n"; mailMessage.From = new MailAddress(settings["SMTPSender"], (!string.IsNullOrEmpty(notification.FromDisplayName)) ? notification.FromDisplayName : site.Name);
} }
mailMessage.Body += "Sent: " + notification.CreatedOn + "\n";
if (!string.IsNullOrEmpty(notification.ToEmail) && !string.IsNullOrEmpty(notification.ToDisplayName)) // recipient
if (!string.IsNullOrEmpty(notification.ToDisplayName))
{ {
mailMessage.To.Add(new MailAddress(notification.ToEmail, notification.ToDisplayName)); mailMessage.To.Add(new MailAddress(notification.ToEmail, notification.ToDisplayName));
mailMessage.Body += "To: " + notification.ToDisplayName + "<" + notification.ToEmail + ">" + "\n";
} }
else else
{ {
mailMessage.To.Add(new MailAddress(notification.ToEmail)); mailMessage.To.Add(new MailAddress(notification.ToEmail));
mailMessage.Body += "To: " + notification.ToEmail + "\n";
} }
mailMessage.Body += "Subject: " + notification.Subject + "\n\n";
mailMessage.Body += notification.Body;
// set encoding // subject
mailMessage.Subject = notification.Subject;
//body
mailMessage.Body = notification.Body;
// encoding
mailMessage.SubjectEncoding = System.Text.Encoding.UTF8; mailMessage.SubjectEncoding = System.Text.Encoding.UTF8;
mailMessage.BodyEncoding = System.Text.Encoding.UTF8; mailMessage.BodyEncoding = System.Text.Encoding.UTF8;

View File

@ -68,7 +68,7 @@ namespace Oqtane.SiteTemplates
new Permission(PermissionNames.View, RoleNames.Admin, true), new Permission(PermissionNames.View, RoleNames.Admin, true),
new Permission(PermissionNames.Edit, RoleNames.Admin, true) new Permission(PermissionNames.Edit, RoleNames.Admin, true)
}.EncodePermissions(), }.EncodePermissions(),
Content = "<p>Copyright (c) 2018-2022 .NET Foundation</p>" + Content = "<p>Copyright (c) 2018-2023 .NET Foundation</p>" +
"<p>Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:</p>" + "<p>Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:</p>" +
"<p>The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.</p>" + "<p>The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.</p>" +
"<p>THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.</p>" "<p>THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.</p>"

View File

@ -72,8 +72,7 @@ namespace Oqtane.Infrastructure
var alias = _siteState?.Alias; var alias = _siteState?.Alias;
if (alias != null) if (alias != null)
{ {
// return tenant details return _tenantRepository.GetTenant(alias.TenantId);
return _tenantRepository.GetTenants().ToList().FirstOrDefault(item => item.TenantId == alias.TenantId);
} }
return null; return null;
} }

View File

@ -60,6 +60,9 @@ namespace Oqtane.Infrastructure
case "3.2.1": case "3.2.1":
Upgrade_3_2_1(tenant, scope); Upgrade_3_2_1(tenant, scope);
break; break;
case "3.3.0":
Upgrade_3_3_0(tenant, scope);
break;
} }
} }
} }
@ -303,5 +306,48 @@ namespace Oqtane.Infrastructure
} }
} }
private void Upgrade_3_3_0(Tenant tenant, IServiceScope scope)
{
try
{
var roles = scope.ServiceProvider.GetRequiredService<IRoleRepository>();
var pages = scope.ServiceProvider.GetRequiredService<IPageRepository>();
var modules = scope.ServiceProvider.GetRequiredService<IModuleRepository>();
var permissions = scope.ServiceProvider.GetRequiredService<IPermissionRepository>();
var siteRepository = scope.ServiceProvider.GetRequiredService<ISiteRepository>();
foreach (Site site in siteRepository.GetSites().ToList())
{
int roleid = roles.GetRoles(site.SiteId).FirstOrDefault(item => item.Name == RoleNames.Registered).RoleId;
int pageid = pages.GetPages(site.SiteId).FirstOrDefault(item => item.Path == "admin").PageId;
var permission = new Permission
{
SiteId = site.SiteId,
EntityName = EntityNames.Page,
EntityId = pageid,
PermissionName = PermissionNames.View,
RoleId = roleid,
IsAuthorized = true
};
permissions.AddPermission(permission);
int moduleid = modules.GetModules(site.SiteId).FirstOrDefault(item => item.ModuleDefinitionName == "Oqtane.Modules.Admin.Dashboard, Oqtane.Client").ModuleId;
permission = new Permission
{
SiteId = site.SiteId,
EntityName = EntityNames.Module,
EntityId = moduleid,
PermissionName = PermissionNames.View,
RoleId = roleid,
IsAuthorized = true
};
permissions.AddPermission(permission);
}
}
catch (Exception ex)
{
Debug.WriteLine($"Oqtane Error: Error In 3.3.0 Upgrade Logic - {ex}");
}
}
} }
} }

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