Compare commits
320 Commits
Author | SHA1 | Date | |
---|---|---|---|
89a166ea9b | |||
391827222e | |||
c1721bd1a1 | |||
f6630ae241 | |||
424cab64a8 | |||
05d2096fb8 | |||
7683af81bc | |||
bad10b3812 | |||
9f9522c2ed | |||
62879c3e52 | |||
45610f8dd7 | |||
18102cbd78 | |||
262d6a1529 | |||
1124ddaf90 | |||
981add3872 | |||
8d4b30140e | |||
b9c59137a8 | |||
0b1c7e06ca | |||
fcaf80cba6 | |||
6358b9eabb | |||
70a3fab1ff | |||
bdf86ace86 | |||
d57132d1e4 | |||
a6e87abf99 | |||
f1771610fe | |||
a88ea9780f | |||
bca0866d72 | |||
cebed93abf | |||
ee2b2e3569 | |||
cb8e9ee244 | |||
486184b16c | |||
9f9bd1988f | |||
ba1bfd1bc0 | |||
e12926e971 | |||
5b4db0de3b | |||
70ff55faa6 | |||
f2bd47d8bc | |||
e2b9c9e98e | |||
b0791a594f | |||
ea5eaa6ed2 | |||
ec3fd1d585 | |||
d76de22977 | |||
c0e3483cc7 | |||
0994cdf3b6 | |||
a76fd82262 | |||
2f919c7d69 | |||
f12592731b | |||
4a20e1a25d | |||
cc7111c3ff | |||
81972aed62 | |||
5e2092c6d4 | |||
d136f8ac91 | |||
5b23917940 | |||
2cda0a3798 | |||
f315ad1ce9 | |||
49f1c273c2 | |||
8518476c87 | |||
a34ed756db | |||
a48232c4e3 | |||
ac65e38390 | |||
eab3a753f5 | |||
9e5922e121 | |||
bf57b23776 | |||
8f4a20fd46 | |||
b3716da5ac | |||
2c129fd800 | |||
6fb18e7a25 | |||
38d28d6944 | |||
a187e1a7a2 | |||
7d7a19c7c2 | |||
6a2ae2153a | |||
f50ba1a91e | |||
b09575dbd6 | |||
c52ee3d91d | |||
1ced5c0425 | |||
e399a5c9b1 | |||
08dff5fb67 | |||
912760f2a7 | |||
4b62fdbf93 | |||
df593d43a7 | |||
89b1fba771 | |||
5505c91ae0 | |||
cc720ff399 | |||
29f07f6c56 | |||
a69e197a1f | |||
6dddd8eff8 | |||
51aada8922 | |||
b47bf40e8f | |||
48151bf365 | |||
659950996d | |||
6e656a4d0a | |||
bf308dd13d | |||
982f3b1943 | |||
7a4ea8cf1b | |||
7c0482a87c | |||
101ededd89 | |||
a8cbc0040e | |||
ed91bb445b | |||
f158a222f4 | |||
46bcad1fca | |||
5e147afb9f | |||
b061d4593f | |||
3fa520b4ef | |||
2df05b4afd | |||
e0569a6748 | |||
2e6ab398d9 | |||
94b03d2a6b | |||
f84fe30bb6 | |||
049ddef531 | |||
a1a214c742 | |||
c40a483ffa | |||
aff99acfae | |||
628129c08d | |||
679d34dfdf | |||
b2f65903ae | |||
2daefe0382 | |||
1214a11704 | |||
e55e0118c2 | |||
a1ac81e907 | |||
14ad68bf69 | |||
e3118c6e99 | |||
b41aeab8f8 | |||
1a738b358e | |||
f4b1e8035b | |||
324e985247 | |||
60faacd7d0 | |||
d4a4d7c346 | |||
189f8f1d27 | |||
ed353461da | |||
e9330d6c62 | |||
f53e7cc3f6 | |||
4f4258d532 | |||
c80910f355 | |||
12470ab178 | |||
704e091e9b | |||
f30f1e5c1f | |||
0741ce2197 | |||
fc81bae9b7 | |||
3fa68e4f96 | |||
05a767c7be | |||
8c1e8f6377 | |||
0fbae8d7da | |||
cec4b339f5 | |||
1a7656d8ee | |||
e173815810 | |||
620c768e05 | |||
7740679077 | |||
1ebc8ebff3 | |||
fa4fac70d5 | |||
8c83a18f93 | |||
a151ecfda0 | |||
dec0c0649c | |||
a356f893ac | |||
e2af4f74c3 | |||
99022b76e5 | |||
9dd6dc7523 | |||
6f588200d7 | |||
f3dbeae28e | |||
9f70361298 | |||
534353ce13 | |||
3f391a7354 | |||
0dd0752710 | |||
710fae4b0e | |||
de6c57a7ee | |||
7eee1fcd6a | |||
1fd2aedf96 | |||
ffdd7c063b | |||
a87af264eb | |||
3640cd2fdd | |||
f7cf25c4bb | |||
77dbd0d4c7 | |||
5a77c83e68 | |||
0a6763e08c | |||
b5aa206670 | |||
dbb4d9b64b | |||
6775edfd66 | |||
b06750ed65 | |||
57879c1891 | |||
65b55a76f2 | |||
8562a68306 | |||
160da46b5a | |||
ae5f70a739 | |||
4456e57466 | |||
24cd090c61 | |||
4f849f5d5f | |||
db2e86e84c | |||
14748ce2b3 | |||
527509732c | |||
aa9214477c | |||
db24ed8b55 | |||
349d1849d9 | |||
188be2fa8c | |||
5c4d7df734 | |||
37de18c670 | |||
0001e3844b | |||
65f171f701 | |||
c4308c239c | |||
2b6af3cb37 | |||
c5a16fbbc1 | |||
1db83f509b | |||
2a06304a2c | |||
7bbe684135 | |||
a996a88fc4 | |||
8cf9e7db51 | |||
ed981c67b7 | |||
6a77a0a5b9 | |||
bfb4b4431b | |||
3de44c0335 | |||
37afd1aec9 | |||
b2ac561673 | |||
c9bf7d9138 | |||
153a689bdb | |||
26b88f1a22 | |||
c66a5d028f | |||
8441c95a5c | |||
e0e32b0199 | |||
2bb76564e9 | |||
86ec25d4de | |||
ed9929963c | |||
36f50118ac | |||
72ddf27504 | |||
9bd36931ff | |||
056ef7a3d5 | |||
e483945d05 | |||
7a9c637e03 | |||
09ce543ea6 | |||
0ef24ebc3f | |||
a4419d3af6 | |||
935983c02a | |||
bd54ce5017 | |||
af6ed78b8e | |||
f72438996d | |||
9db2a55a5a | |||
950d90badb | |||
1864d180af | |||
0e82e98382 | |||
46023d35dc | |||
90d2e0a40b | |||
5f884e0796 | |||
16477052e2 | |||
66a05603f7 | |||
fe2a883386 | |||
ca7fdaa125 | |||
1283ec2008 | |||
d1f78f9048 | |||
45f43bfade | |||
4793ab4bc9 | |||
88b174dea8 | |||
06ca382bd7 | |||
b09175a8db | |||
677f68b08d | |||
8058b8dba4 | |||
4bc26f13c1 | |||
e6cf77e724 | |||
f8737c112e | |||
74b72ed9d4 | |||
4950391201 | |||
64a38d6e45 | |||
e2d8ee53f8 | |||
0204ff8dd5 | |||
4630ee6e93 | |||
c4f2abf143 | |||
e7444a0194 | |||
ffed7305ed | |||
334054bcd4 | |||
5bb98eb5b2 | |||
e842bd882a | |||
74bfb46f73 | |||
cd45bf4b70 | |||
51600bbcb0 | |||
8811a9bcaa | |||
4521f8a774 | |||
5b427783f8 | |||
9508983b15 | |||
fd09912cd7 | |||
01cc8584b6 | |||
c0b104e7c8 | |||
9a82021a82 | |||
1fb54a0b0f | |||
aa5ea61638 | |||
a59ec0258b | |||
b403f5cf71 | |||
0ac6a62b86 | |||
ed3743d3b6 | |||
3468cba000 | |||
5a4cdc5354 | |||
af4e19a57e | |||
26bb743679 | |||
96cc726e22 | |||
f4b00b01d0 | |||
127b2ca86d | |||
4b8b93e1b8 | |||
3aea412fe9 | |||
2aef96ad4f | |||
ec0a77230c | |||
b35e4bddd0 | |||
aa32beb341 | |||
efafe89b42 | |||
5ef2e49d9c | |||
1cfbf61a30 | |||
2bb5494b84 | |||
e8a41ccb47 | |||
7184f7f635 | |||
cc5727b7fa | |||
7f3d6ef6a5 | |||
44ce68097b | |||
d976cc6c19 | |||
d19d7d2a43 | |||
9bfaa02f97 | |||
2d9396b245 | |||
56e0da64ee | |||
997e9213f2 | |||
366569a23b | |||
36d5747b4f | |||
ea026c726c | |||
1e71e32c74 | |||
ed729bbd4f | |||
1a925221b7 | |||
af7b4db062 | |||
cfefe35e3f |
25
.gitea/workflows/build-container.yml
Normal file
25
.gitea/workflows/build-container.yml
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
name: build-docker-imge
|
||||||
|
on:
|
||||||
|
- push
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build the docker container
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: "Git clone"
|
||||||
|
run: git clone ${{ gitea.server_url }}/${{ gitea.repository }}.git .
|
||||||
|
- name: "Git checkout"
|
||||||
|
run: git checkout "${{ gitea.sha }}"
|
||||||
|
- uses: aevea/action-kaniko@master
|
||||||
|
name: Run Kaniko to build our api docker container.
|
||||||
|
with:
|
||||||
|
image: kocoded/oqtane.framework
|
||||||
|
tag: ${{ git.workflow_sha }}
|
||||||
|
tag_with_latest: github.ref == 'refs/heads/master'
|
||||||
|
registry: git.kocoder.xyz
|
||||||
|
username: ${{ secrets.CI_RUNNER_USER }}
|
||||||
|
password: ${{ secrets.CI_RUNNER_TOKEN }}
|
||||||
|
build_file: Dockerfile
|
||||||
|
target: deploy
|
||||||
|
|
24
Dockerfile
Normal file
24
Dockerfile
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# Build
|
||||||
|
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
|
||||||
|
|
||||||
|
WORKDIR /source
|
||||||
|
|
||||||
|
COPY --link . .
|
||||||
|
|
||||||
|
RUN dotnet restore /source/Oqtane.sln
|
||||||
|
|
||||||
|
RUN dotnet build "/source/Oqtane.sln" -c Release -o /source/build/
|
||||||
|
|
||||||
|
# Publish
|
||||||
|
FROM build AS publish
|
||||||
|
|
||||||
|
RUN dotnet publish "Oqtane.Server/Oqtane.Server.csproj" -c Release -o /source/publish/
|
||||||
|
|
||||||
|
# Deploy
|
||||||
|
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS deploy
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
|
||||||
|
COPY --from=publish /source/publish/ /app/
|
||||||
|
ENTRYPOINT ["dotnet", "Oqtane.Server.dll"]
|
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2018-2024 .NET Foundation
|
Copyright (c) 2018-2025 .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
|
||||||
|
@ -52,6 +52,8 @@ namespace Microsoft.Extensions.DependencyInjection
|
|||||||
services.AddScoped<IVisitorService, VisitorService>();
|
services.AddScoped<IVisitorService, VisitorService>();
|
||||||
services.AddScoped<ISyncService, SyncService>();
|
services.AddScoped<ISyncService, SyncService>();
|
||||||
services.AddScoped<ILocalizationCookieService, LocalizationCookieService>();
|
services.AddScoped<ILocalizationCookieService, LocalizationCookieService>();
|
||||||
|
services.AddScoped<ICookieConsentService, CookieConsentService>();
|
||||||
|
services.AddScoped<IOutputCacheService, OutputCacheService>();
|
||||||
|
|
||||||
// providers
|
// providers
|
||||||
services.AddScoped<ITextEditor, Oqtane.Modules.Controls.QuillJSTextEditor>();
|
services.AddScoped<ITextEditor, Oqtane.Modules.Controls.QuillJSTextEditor>();
|
||||||
|
@ -71,14 +71,14 @@
|
|||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="username" HelpText="Provide a username for the primary user account" ResourceKey="Username">Username:</Label>
|
<Label Class="col-sm-3" For="username" HelpText="Provide a username for the primary user account" ResourceKey="Username">Username:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="username" type="text" class="form-control" @bind="@_hostUsername" />
|
<input id="username" type="text" class="form-control" maxlength="256" @bind="@_hostUsername" />
|
||||||
</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="password" HelpText="Provide a password for the primary user account" ResourceKey="Password">Password:</Label>
|
<Label Class="col-sm-3" For="password" HelpText="Provide a password for the primary user account" ResourceKey="Password">Password:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input id="password" type="@_passwordType" class="form-control" @bind="@_hostPassword" autocomplete="new-password" />
|
<input id="password" type="@_passwordType" class="form-control" maxlength="256" @bind="@_hostPassword" autocomplete="new-password" />
|
||||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglePassword</button>
|
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglePassword</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -87,7 +87,7 @@
|
|||||||
<Label Class="col-sm-3" For="confirm" HelpText="Please confirm the password entered above by entering it again" ResourceKey="Confirm">Confirm:</Label>
|
<Label Class="col-sm-3" For="confirm" HelpText="Please confirm the password entered above by entering it again" ResourceKey="Confirm">Confirm:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input id="confirm" type="@_confirmPasswordType" class="form-control" @bind="@_confirmPassword" autocomplete="new-password" />
|
<input id="confirm" type="@_confirmPasswordType" class="form-control" maxlength="256" @bind="@_confirmPassword" autocomplete="new-password" />
|
||||||
<button type="button" class="btn btn-secondary" @onclick="@ToggleConfirmPassword" tabindex="-1">@_toggleConfirmPassword</button>
|
<button type="button" class="btn btn-secondary" @onclick="@ToggleConfirmPassword" tabindex="-1">@_toggleConfirmPassword</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -95,7 +95,13 @@
|
|||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="email" HelpText="Provide the email address for the host user account" ResourceKey="Email">Email:</Label>
|
<Label Class="col-sm-3" For="email" HelpText="Provide the email address for the host user account" ResourceKey="Email">Email:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input type="text" class="form-control" @bind="@_hostEmail" />
|
<input type="text" class="form-control" maxlength="256" @bind="@_hostEmail" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="name" HelpText="Provide the full name of the host user" ResourceKey="Name">Full Name:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input type="text" class="form-control" maxlength="50" @bind="@_hostName" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@ -153,6 +159,7 @@
|
|||||||
private string _toggleConfirmPassword = string.Empty;
|
private string _toggleConfirmPassword = string.Empty;
|
||||||
private string _confirmPassword = string.Empty;
|
private string _confirmPassword = string.Empty;
|
||||||
private string _hostEmail = string.Empty;
|
private string _hostEmail = string.Empty;
|
||||||
|
private string _hostName = string.Empty;
|
||||||
private List<SiteTemplate> _templates;
|
private List<SiteTemplate> _templates;
|
||||||
private string _template = Constants.DefaultSiteTemplate;
|
private string _template = Constants.DefaultSiteTemplate;
|
||||||
private bool _register = true;
|
private bool _register = true;
|
||||||
@ -236,7 +243,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (connectionString != "" && !string.IsNullOrEmpty(_hostUsername) && !string.IsNullOrEmpty(_hostPassword) && _hostPassword == _confirmPassword && !string.IsNullOrEmpty(_hostEmail) && _hostEmail.Contains("@"))
|
if (connectionString != "" && !string.IsNullOrEmpty(_hostUsername) && !string.IsNullOrEmpty(_hostPassword) && _hostPassword == _confirmPassword && !string.IsNullOrEmpty(_hostEmail) && _hostEmail.Contains("@") && !string.IsNullOrEmpty(_hostName))
|
||||||
{
|
{
|
||||||
var result = await UserService.ValidateUserAsync(_hostUsername, _hostEmail, _hostPassword);
|
var result = await UserService.ValidateUserAsync(_hostUsername, _hostEmail, _hostPassword);
|
||||||
if (result.Succeeded)
|
if (result.Succeeded)
|
||||||
@ -256,7 +263,7 @@
|
|||||||
HostUsername = _hostUsername,
|
HostUsername = _hostUsername,
|
||||||
HostPassword = _hostPassword,
|
HostPassword = _hostPassword,
|
||||||
HostEmail = _hostEmail,
|
HostEmail = _hostEmail,
|
||||||
HostName = _hostUsername,
|
HostName = _hostName,
|
||||||
TenantName = TenantNames.Master,
|
TenantName = TenantNames.Master,
|
||||||
IsNewTenant = true,
|
IsNewTenant = true,
|
||||||
SiteName = Constants.DefaultSite,
|
SiteName = Constants.DefaultSite,
|
||||||
|
@ -49,18 +49,24 @@
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="imagesizes" HelpText="Enter a list of image sizes which can be generated dynamically from uploaded images (ie. 200x200,400x400). Use * to indicate the folder supports all image sizes." ResourceKey="ImageSizes">Image Sizes: </Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<input id="imagesizes" class="form-control" @bind="@_imagesizes" maxlength="512" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="capacity" HelpText="Enter the maximum folder capacity (in megabytes). Specify zero if the capacity is unlimited." ResourceKey="Capacity">Capacity: </Label>
|
<Label Class="col-sm-3" For="capacity" HelpText="Enter the maximum folder capacity (in megabytes). Specify zero if the capacity is unlimited." ResourceKey="Capacity">Capacity: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="capacity" class="form-control" @bind="@_capacity" required />
|
<input id="capacity" class="form-control" @bind="@_capacity" required />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="cachecontrol" HelpText="Optionally provide a Cache-Control directive for this folder. For example 'public, max-age=60' indicates that files in this folder should be cached for 60 seconds. Please note that when caching is enabled, changes to files will not be immediately reflected in the UI." ResourceKey="CacheControl">Caching: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="cachecontrol" class="form-control" @bind="@_cachecontrol" maxlength="50" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="imagesizes" HelpText="Optionally enter a list of image sizes which can be generated dynamically from uploaded images (ie. 200x200,400x400). Use * to indicate the folder supports all image sizes (not recommended)." ResourceKey="ImageSizes">Image Sizes: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="imagesizes" class="form-control" @bind="@_imagesizes" maxlength="512" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@if (PageState.QueryString.ContainsKey("id"))
|
@if (PageState.QueryString.ContainsKey("id"))
|
||||||
{
|
{
|
||||||
@ -100,8 +106,9 @@
|
|||||||
private int _parentId = -1;
|
private int _parentId = -1;
|
||||||
private string _name;
|
private string _name;
|
||||||
private string _type = FolderTypes.Private;
|
private string _type = FolderTypes.Private;
|
||||||
private string _imagesizes = string.Empty;
|
|
||||||
private string _capacity = "0";
|
private string _capacity = "0";
|
||||||
|
private string _cachecontrol = string.Empty;
|
||||||
|
private string _imagesizes = string.Empty;
|
||||||
private bool _isSystem;
|
private bool _isSystem;
|
||||||
private List<Permission> _permissions = null;
|
private List<Permission> _permissions = null;
|
||||||
private string _createdBy;
|
private string _createdBy;
|
||||||
@ -132,8 +139,9 @@
|
|||||||
_parentId = folder.ParentId ?? -1;
|
_parentId = folder.ParentId ?? -1;
|
||||||
_name = folder.Name;
|
_name = folder.Name;
|
||||||
_type = folder.Type;
|
_type = folder.Type;
|
||||||
_imagesizes = folder.ImageSizes;
|
|
||||||
_capacity = folder.Capacity.ToString();
|
_capacity = folder.Capacity.ToString();
|
||||||
|
_cachecontrol = folder.CacheControl;
|
||||||
|
_imagesizes = folder.ImageSizes;
|
||||||
_isSystem = folder.IsSystem;
|
_isSystem = folder.IsSystem;
|
||||||
_permissions = folder.PermissionList;
|
_permissions = folder.PermissionList;
|
||||||
_createdBy = folder.CreatedBy;
|
_createdBy = folder.CreatedBy;
|
||||||
@ -204,8 +212,9 @@
|
|||||||
folder.SiteId = PageState.Site.SiteId;
|
folder.SiteId = PageState.Site.SiteId;
|
||||||
folder.Name = _name;
|
folder.Name = _name;
|
||||||
folder.Type = _type;
|
folder.Type = _type;
|
||||||
folder.ImageSizes = _imagesizes;
|
|
||||||
folder.Capacity = int.Parse(_capacity);
|
folder.Capacity = int.Parse(_capacity);
|
||||||
|
folder.CacheControl = _cachecontrol;
|
||||||
|
folder.ImageSizes = _imagesizes;
|
||||||
folder.IsSystem = _isSystem;
|
folder.IsSystem = _isSystem;
|
||||||
folder.PermissionList = _permissionGrid.GetPermissionList();
|
folder.PermissionList = _permissionGrid.GetPermissionList();
|
||||||
|
|
||||||
|
@ -3,11 +3,20 @@
|
|||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject IFolderService FolderService
|
@inject IFolderService FolderService
|
||||||
@inject IFileService FileService
|
@inject IFileService FileService
|
||||||
|
@inject ISettingService SettingService
|
||||||
@inject IStringLocalizer<Index> Localizer
|
@inject IStringLocalizer<Index> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
@if (_files != null)
|
@if (_files == null)
|
||||||
{
|
{
|
||||||
|
<p>
|
||||||
|
<em>@SharedLocalizer["Loading"]</em>
|
||||||
|
</p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<TabStrip>
|
||||||
|
<TabPanel Name="Files" Heading="Files" ResourceKey="Files">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md mb-1">
|
<div class="col-md mb-1">
|
||||||
<ActionLink Action="Edit" Text="Add Folder" Class="btn btn-secondary" ResourceKey="AddFolder" />
|
<ActionLink Action="Edit" Text="Add Folder" Class="btn btn-secondary" ResourceKey="AddFolder" />
|
||||||
@ -29,6 +38,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@if (_files.Count != 0)
|
||||||
|
{
|
||||||
<Pager Items="@_files" SearchProperties="Name">
|
<Pager Items="@_files" SearchProperties="Name">
|
||||||
<Header>
|
<Header>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
@ -47,10 +58,37 @@
|
|||||||
<td>@string.Format("{0:0.00}", ((decimal)context.Size / 1000)) KB</td>
|
<td>@string.Format("{0:0.00}", ((decimal)context.Size / 1000)) KB</td>
|
||||||
</Row>
|
</Row>
|
||||||
</Pager>
|
</Pager>
|
||||||
@if (_files.Count == 0)
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
<div class="text-center">@Localizer["NoFiles"]</div>
|
<div class="text-center">@Localizer["NoFiles"]</div>
|
||||||
}
|
}
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings" Security="SecurityAccessLevel.Admin">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="imageExt" HelpText="Enter a comma separated list of image file extensions" ResourceKey="ImageExtensions">Image Extensions: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="imageExt" spellcheck="false" class="form-control" @bind="@_imageFiles" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="uploadableFileExt" HelpText="Enter a comma separated list of uploadable file extensions" ResourceKey="UploadableFileExtensions">Uploadable File Extensions: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="uploadableFileExt" spellcheck="false" class="form-control" @bind="@_uploadableFiles" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="maxChunkSize" HelpText="Files are split into chunks to streamline the upload process. Specify the maximum chunk size in MB (note that higher chunk sizes should only be used on faster networks)." ResourceKey="MaxChunkSize">Max Upload Chunk Size (MB): </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="maxChunkSize" type="number" min="1" max="10" step="1" class="form-control" @bind="@_maxChunkSize" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<button type="button" class="btn btn-success" @onclick="SaveSiteSettings">@SharedLocalizer["Save"]</button>
|
||||||
|
</TabPanel>
|
||||||
|
</TabStrip>
|
||||||
}
|
}
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
@ -58,6 +96,10 @@
|
|||||||
private int _folderId = -1;
|
private int _folderId = -1;
|
||||||
private List<File> _files;
|
private List<File> _files;
|
||||||
|
|
||||||
|
private string _imageFiles = string.Empty;
|
||||||
|
private string _uploadableFiles = string.Empty;
|
||||||
|
private int _maxChunkSize = 1;
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
||||||
|
|
||||||
protected override async Task OnParametersSetAsync()
|
protected override async Task OnParametersSetAsync()
|
||||||
@ -71,6 +113,13 @@
|
|||||||
_folderId = _folders[0].FolderId;
|
_folderId = _folders[0].FolderId;
|
||||||
await GetFiles();
|
await GetFiles();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||||
|
_imageFiles = SettingService.GetSetting(settings, "ImageFiles", Constants.ImageFiles);
|
||||||
|
_imageFiles = (string.IsNullOrEmpty(_imageFiles)) ? Constants.ImageFiles : _imageFiles;
|
||||||
|
_uploadableFiles = SettingService.GetSetting(settings, "UploadableFiles", Constants.UploadableFiles);
|
||||||
|
_uploadableFiles = (string.IsNullOrEmpty(_uploadableFiles)) ? Constants.UploadableFiles : _uploadableFiles;
|
||||||
|
_maxChunkSize = int.Parse(SettingService.GetSetting(settings, "MaxChunkSize", "1"));
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -115,4 +164,23 @@
|
|||||||
AddModuleMessage(string.Format(Localizer["Error.File.Delete"], file.Name), MessageType.Error);
|
AddModuleMessage(string.Format(Localizer["Error.File.Delete"], file.Name), MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task SaveSiteSettings()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||||
|
settings = SettingService.SetSetting(settings, "ImageFiles", (_imageFiles != Constants.ImageFiles) ? _imageFiles.Replace(" ", "") : "", false);
|
||||||
|
settings = SettingService.SetSetting(settings, "UploadableFiles", (_uploadableFiles != Constants.UploadableFiles) ? _uploadableFiles.Replace(" ", "") : "", false);
|
||||||
|
settings = SettingService.SetSetting(settings, "MaxChunkSize", _maxChunkSize.ToString(), false);
|
||||||
|
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
|
||||||
|
AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Saving Site Settings {Error}", ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.SaveSiteSettings"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -55,10 +55,6 @@ else
|
|||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
await GetJobs();
|
await GetJobs();
|
||||||
if (_jobs.Count == 0)
|
|
||||||
{
|
|
||||||
AddModuleMessage(string.Format(Localizer["Message.NoJobs"], NavigateUrl("admin/system")), MessageType.Warning);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task GetJobs()
|
private async Task GetJobs()
|
||||||
|
@ -29,12 +29,12 @@ else
|
|||||||
{
|
{
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<Label Class="control-label" For="username" HelpText="Please enter your Username" ResourceKey="Username">Username:</Label>
|
<Label Class="control-label" For="username" HelpText="Please enter your Username" ResourceKey="Username">Username:</Label>
|
||||||
<input id="username" type="text" @ref="username" class="form-control" placeholder="@Localizer["Username.Placeholder"]" @bind="@_username" required />
|
<input id="username" type="text" @ref="username" class="form-control" placeholder="@Localizer["Username.Placeholder"]" @bind="@_username" @bind:event="oninput" required />
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group mt-2">
|
<div class="form-group mt-2">
|
||||||
<Label Class="control-label" For="password" HelpText="Please enter your Password" ResourceKey="Password">Password:</Label>
|
<Label Class="control-label" For="password" HelpText="Please enter your Password" ResourceKey="Password">Password:</Label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input id="password" type="@_passwordtype" name="Password" class="form-control" placeholder="@Localizer["Password.Placeholder"]" @bind="@_password" required />
|
<input id="password" type="@_passwordtype" name="Password" class="form-control" placeholder="@Localizer["Password.Placeholder"]" @bind="@_password" @bind:event="oninput" required />
|
||||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -111,6 +111,8 @@
|
|||||||
private async Task CreateModule()
|
private async Task CreateModule()
|
||||||
{
|
{
|
||||||
validated = true;
|
validated = true;
|
||||||
|
_owner = _owner.Trim();
|
||||||
|
_module = _module.Trim();
|
||||||
var interop = new Interop(JSRuntime);
|
var interop = new Interop(JSRuntime);
|
||||||
if (await interop.FormValid(form))
|
if (await interop.FormValid(form))
|
||||||
{
|
{
|
||||||
|
@ -63,24 +63,7 @@
|
|||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="packagename" HelpText="The unique name of the package from which this module was installed. This value must be specified within the module's IModule interface specification." ResourceKey="PackageName">Package Name: </Label>
|
<Label Class="col-sm-3" For="packagename" HelpText="The unique name of the package from which this module was installed. This value must be specified within the module's IModule interface specification." ResourceKey="PackageName">Package Name: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@if (!string.IsNullOrEmpty(_packagename))
|
|
||||||
{
|
|
||||||
<div class="input-group">
|
|
||||||
<input id="packagename" class="form-control" @bind="@_packagename" disabled />
|
<input id="packagename" class="form-control" @bind="@_packagename" disabled />
|
||||||
@if (string.IsNullOrEmpty(_packageurl))
|
|
||||||
{
|
|
||||||
<button type="button" class="btn btn-secondary" @onclick="ValidatePackage">@Localizer["Validate"]</button>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<a href="@_packageurl" target="_blank" class="btn btn-primary">@SharedLocalizer["Download"]</a>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<input id="packagename" class="form-control" @bind="@_packagename" disabled />
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@ -244,7 +227,6 @@
|
|||||||
private string _moduledefinitionname = "";
|
private string _moduledefinitionname = "";
|
||||||
private string _version;
|
private string _version;
|
||||||
private string _packagename = "";
|
private string _packagename = "";
|
||||||
private string _packageurl = "";
|
|
||||||
private string _owner = "";
|
private string _owner = "";
|
||||||
private string _url = "";
|
private string _url = "";
|
||||||
private string _contact = "";
|
private string _contact = "";
|
||||||
@ -445,27 +427,5 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ValidatePackage()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var package = await PackageService.GetPackageAsync(_packagename, _version, true);
|
|
||||||
if (package == null || string.IsNullOrEmpty(package.PackageUrl))
|
|
||||||
{
|
|
||||||
AddModuleMessage(Localizer["Message.Validate"], MessageType.Warning);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_packageurl = package.PackageUrl;
|
|
||||||
AddModuleMessage(Localizer["Message.Download"], MessageType.Info);
|
|
||||||
}
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
await logger.LogError(ex, "Error Downloading Package {PackageId} {Version}", _packagename, _version);
|
|
||||||
AddModuleMessage(Localizer["Error.Validate"], MessageType.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private string Browse(Page page) => string.IsNullOrEmpty(page.Url) ? NavigateUrl(page.Path) : page.Url;
|
private string Browse(Page page) => string.IsNullOrEmpty(page.Url) ? NavigateUrl(page.Path) : page.Url;
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="name" HelpText="Enter the page name" ResourceKey="Name">Name: </Label>
|
<Label Class="col-sm-3" For="name" HelpText="Enter the page name" ResourceKey="Name">Name: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="name" class="form-control" @bind="@_name" required />
|
<input id="name" class="form-control" @bind="@_name" maxlength="50" required />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||||
@ -101,13 +101,13 @@
|
|||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="path" HelpText="Optionally enter a url path for this page (ie. home ). If you do not provide a url path, the page name will be used. If the page is intended to be the root path specify '/'." ResourceKey="UrlPath">Url Path: </Label>
|
<Label Class="col-sm-3" For="path" HelpText="Optionally enter a url path for this page (ie. home ). If you do not provide a url path, the page name will be used. If the page is intended to be the root path specify '/'." ResourceKey="UrlPath">Url Path: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="path" class="form-control" @bind="@_path" />
|
<input id="path" class="form-control" @bind="@_path" maxlength="256" />
|
||||||
</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="url" HelpText="Optionally enter a url which this page should redirect to when a user navigates to it" ResourceKey="Redirect">Redirect: </Label>
|
<Label Class="col-sm-3" For="url" HelpText="Optionally enter a url which this page should redirect to when a user navigates to it" ResourceKey="Redirect">Redirect: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="url" class="form-control" @bind="@_url" />
|
<input id="url" class="form-control" @bind="@_url" maxlength="500" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@ -147,7 +147,7 @@
|
|||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="title" HelpText="Optionally enter the page title. If you do not provide a page title, the page name will be used." ResourceKey="Title">Title: </Label>
|
<Label Class="col-sm-3" For="title" HelpText="Optionally enter the page title. If you do not provide a page title, the page name will be used." ResourceKey="Title">Title: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="title" class="form-control" @bind="@_title" />
|
<input id="title" class="form-control" @bind="@_title" maxlength="200" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@ -186,13 +186,13 @@
|
|||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="headcontent" HelpText="Optionally enter content to be included in the page head (ie. meta, link, or script tags)" ResourceKey="HeadContent">Head Content: </Label>
|
<Label Class="col-sm-3" For="headcontent" HelpText="Optionally enter content to be included in the page head (ie. meta, link, or script tags)" ResourceKey="HeadContent">Head Content: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<textarea id="headcontent" class="form-control" @bind="@_headcontent" rows="3"></textarea>
|
<textarea id="headcontent" class="form-control" @bind="@_headcontent" rows="3" maxlength="4000"></textarea>
|
||||||
</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="bodycontent" HelpText="Optionally enter content to be included in the page body (ie. script tags)" ResourceKey="BodyContent">Body Content: </Label>
|
<Label Class="col-sm-3" For="bodycontent" HelpText="Optionally enter content to be included in the page body (ie. script tags)" ResourceKey="BodyContent">Body Content: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<textarea id="bodycontent" class="form-control" @bind="@_bodycontent" rows="3"></textarea>
|
<textarea id="bodycontent" class="form-control" @bind="@_bodycontent" rows="3" maxlength="4000"></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -116,7 +116,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="path" HelpText="Optionally enter a url path for this page (ie. home ). If you do not provide a url path, the page name will be used. If the page is intended to be the root path specify '/'." ResourceKey="UrlPath">Url Path: </Label>
|
<Label Class="col-sm-3" For="path" HelpText="Optionally enter a url path for this page (ie. home ). If you do not provide a url path, the page name will be used. Please note that spaces and punctuation will be replaced by a dash. If the page is intended to be the root path specify '/'." ResourceKey="UrlPath">Url Path: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="path" class="form-control" @bind="@_path" maxlength="256" />
|
<input id="path" class="form-control" @bind="@_path" maxlength="256" />
|
||||||
</div>
|
</div>
|
||||||
@ -205,13 +205,13 @@
|
|||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="headcontent" HelpText="Optionally enter content to be included in the page head (ie. meta, link, or script tags)" ResourceKey="HeadContent">Head Content: </Label>
|
<Label Class="col-sm-3" For="headcontent" HelpText="Optionally enter content to be included in the page head (ie. meta, link, or script tags)" ResourceKey="HeadContent">Head Content: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<textarea id="headcontent" class="form-control" @bind="@_headcontent" rows="3"></textarea>
|
<textarea id="headcontent" class="form-control" @bind="@_headcontent" rows="3" maxlength="4000"></textarea>
|
||||||
</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="bodycontent" HelpText="Optionally enter content to be included in the page body (ie. script tags)" ResourceKey="BodyContent">Body Content: </Label>
|
<Label Class="col-sm-3" For="bodycontent" HelpText="Optionally enter content to be included in the page body (ie. script tags)" ResourceKey="BodyContent">Body Content: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<textarea id="bodycontent" class="form-control" @bind="@_bodycontent" rows="3"></textarea>
|
<textarea id="bodycontent" class="form-control" @bind="@_bodycontent" rows="3" maxlength="4000"></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -263,6 +263,12 @@
|
|||||||
<input id="title" class="form-control" @bind="@_title" maxlength="200" />
|
<input id="title" class="form-control" @bind="@_title" maxlength="200" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="path" HelpText="Provide a url path for your personalized page. Please note that spaces and punctuation will be replaced by a dash." ResourceKey="PersonalizedUrlPath">Url Path: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="path" class="form-control" @bind="@_path" maxlength="256" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="theme" HelpText="Select the theme for this page" ResourceKey="Theme">Theme: </Label>
|
<Label Class="col-sm-3" For="theme" HelpText="Select the theme for this page" ResourceKey="Theme">Theme: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
|
@ -26,14 +26,14 @@ else
|
|||||||
<Header>
|
<Header>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
<th>@SharedLocalizer["Name"]</th>
|
<th>@SharedLocalizer["Path"]</th>
|
||||||
<th>@Localizer["DeletedBy"]</th>
|
<th>@Localizer["DeletedBy"]</th>
|
||||||
<th>@Localizer["DeletedOn"]</th>
|
<th>@Localizer["DeletedOn"]</th>
|
||||||
</Header>
|
</Header>
|
||||||
<Row>
|
<Row>
|
||||||
<td><button type="button" @onclick="@(() => RestorePage(context))" class="btn btn-success" title="Restore">@Localizer["Restore"]</button></td>
|
<td><button type="button" @onclick="@(() => RestorePage(context))" class="btn btn-success" title="Restore">@Localizer["Restore"]</button></td>
|
||||||
<td><ActionDialog Header="Delete Page" Message="@string.Format(Localizer["Confirm.Page.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeletePage(context))" ResourceKey="DeletePage" /></td>
|
<td><ActionDialog Header="Delete Page" Message="@string.Format(Localizer["Confirm.Page.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeletePage(context))" ResourceKey="DeletePage" /></td>
|
||||||
<td>@context.Name</td>
|
<td>@context.Path</td>
|
||||||
<td>@context.DeletedBy</td>
|
<td>@context.DeletedBy</td>
|
||||||
<td>@context.DeletedOn</td>
|
<td>@context.DeletedOn</td>
|
||||||
</Row>
|
</Row>
|
||||||
@ -104,13 +104,26 @@ else
|
|||||||
private async Task RestorePage(Page page)
|
private async Task RestorePage(Page page)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
{
|
||||||
|
var validated = true;
|
||||||
|
if (page.ParentId != null)
|
||||||
|
{
|
||||||
|
var parent = _pages.Find(item => item.PageId == page.ParentId);
|
||||||
|
validated = !parent.IsDeleted;
|
||||||
|
}
|
||||||
|
if (validated)
|
||||||
{
|
{
|
||||||
page.IsDeleted = false;
|
page.IsDeleted = false;
|
||||||
await PageService.UpdatePageAsync(page);
|
await PageService.UpdatePageAsync(page);
|
||||||
await logger.LogInformation("Page Restored {Page}", page);
|
await logger.LogInformation("Page Restored {Page}", page);
|
||||||
|
AddModuleMessage(Localizer["Success.Page.Restore"], MessageType.Success);
|
||||||
await Load();
|
await Load();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
NavigationManager.NavigateTo(NavigateUrl());
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddModuleMessage(Localizer["Message.Page.Restore"], MessageType.Warning);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -125,9 +138,9 @@ else
|
|||||||
{
|
{
|
||||||
await PageService.DeletePageAsync(page.PageId);
|
await PageService.DeletePageAsync(page.PageId);
|
||||||
await logger.LogInformation("Page Permanently Deleted {Page}", page);
|
await logger.LogInformation("Page Permanently Deleted {Page}", page);
|
||||||
|
AddModuleMessage(Localizer["Success.Page.Delete"], MessageType.Success);
|
||||||
await Load();
|
await Load();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
NavigationManager.NavigateTo(NavigateUrl());
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -148,10 +161,10 @@ else
|
|||||||
}
|
}
|
||||||
|
|
||||||
await logger.LogInformation("Pages Permanently Deleted");
|
await logger.LogInformation("Pages Permanently Deleted");
|
||||||
|
AddModuleMessage(Localizer["Success.Pages.Delete"], MessageType.Success);
|
||||||
await Load();
|
await Load();
|
||||||
HideProgressIndicator();
|
HideProgressIndicator();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
NavigationManager.NavigateTo(NavigateUrl());
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -169,6 +182,7 @@ else
|
|||||||
pagemodule.IsDeleted = false;
|
pagemodule.IsDeleted = false;
|
||||||
await PageModuleService.UpdatePageModuleAsync(pagemodule);
|
await PageModuleService.UpdatePageModuleAsync(pagemodule);
|
||||||
await logger.LogInformation("Module Restored {Module}", module);
|
await logger.LogInformation("Module Restored {Module}", module);
|
||||||
|
AddModuleMessage(Localizer["Success.Module.Restore"], MessageType.Success);
|
||||||
await Load();
|
await Load();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
@ -185,6 +199,7 @@ else
|
|||||||
{
|
{
|
||||||
await PageModuleService.DeletePageModuleAsync(module.PageModuleId);
|
await PageModuleService.DeletePageModuleAsync(module.PageModuleId);
|
||||||
await logger.LogInformation("Module Permanently Deleted {Module}", module);
|
await logger.LogInformation("Module Permanently Deleted {Module}", module);
|
||||||
|
AddModuleMessage(Localizer["Success.Module.Delete"], MessageType.Success);
|
||||||
await Load();
|
await Load();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
@ -205,6 +220,7 @@ else
|
|||||||
await PageModuleService.DeletePageModuleAsync(module.PageModuleId);
|
await PageModuleService.DeletePageModuleAsync(module.PageModuleId);
|
||||||
}
|
}
|
||||||
await logger.LogInformation("Modules Permanently Deleted");
|
await logger.LogInformation("Modules Permanently Deleted");
|
||||||
|
AddModuleMessage(Localizer["Success.Modules.Delete"], MessageType.Success);
|
||||||
await Load();
|
await Load();
|
||||||
HideProgressIndicator();
|
HideProgressIndicator();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
|
@ -56,6 +56,19 @@
|
|||||||
<input id="displayname" class="form-control" @bind="@_displayname" maxlength="50" />
|
<input id="displayname" class="form-control" @bind="@_displayname" maxlength="50" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="subject" HelpText="Deine Studien- oder Ausbildungsrichtung" ResourceKey="Subject">Fachrichtung:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="subject" class="form-control" @bind="@_subject" maxlength="100" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="graduationYear" HelpText="Dein voraussichtlicher Abschlussjahrgang" ResourceKey="GraduationYear">Abschlussjahrgang:</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="graduationYear" type="number" class="form-control" @bind="@_graduationYear" min="1950" max="2100" step="1" required />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
<button type="button" class="btn btn-primary" @onclick="Register">@Localizer["Register"]</button>
|
<button type="button" class="btn btn-primary" @onclick="Register">@Localizer["Register"]</button>
|
||||||
@ -89,6 +102,9 @@ else
|
|||||||
private string _displayname = string.Empty;
|
private string _displayname = string.Empty;
|
||||||
private bool _userCreated = false;
|
private bool _userCreated = false;
|
||||||
private bool _allowsitelogin = true;
|
private bool _allowsitelogin = true;
|
||||||
|
private string _subject = string.Empty;
|
||||||
|
private int _graduationYear;
|
||||||
|
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous;
|
||||||
|
|
||||||
@ -124,7 +140,10 @@ else
|
|||||||
Password = _password,
|
Password = _password,
|
||||||
Email = _email,
|
Email = _email,
|
||||||
DisplayName = (_displayname == string.Empty ? _username : _displayname),
|
DisplayName = (_displayname == string.Empty ? _username : _displayname),
|
||||||
PhotoFileId = null
|
PhotoFileId = null,
|
||||||
|
Subject = _subject,
|
||||||
|
GraduationYear = _graduationYear
|
||||||
|
|
||||||
};
|
};
|
||||||
user = await UserService.AddUserAsync(user);
|
user = await UserService.AddUserAsync(user);
|
||||||
|
|
||||||
|
@ -58,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.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" />
|
<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.User.Username == UserNames.Host || context.User.UserId == PageState.User.UserId)" ResourceKey="DeleteUserRole" />
|
||||||
</td>
|
</td>
|
||||||
</Row>
|
</Row>
|
||||||
</Pager>
|
</Pager>
|
||||||
@ -179,15 +179,21 @@ else
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async Task DeleteUserRole(int UserRoleId)
|
private async Task DeleteUserRole(int UserRoleId)
|
||||||
{
|
|
||||||
validated = true;
|
|
||||||
var interop = new Interop(JSRuntime);
|
|
||||||
if (await interop.FormValid(form))
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
{
|
||||||
|
var userrole = await UserRoleService.GetUserRoleAsync(UserRoleId);
|
||||||
|
if (userrole.Role.Name == RoleNames.Registered)
|
||||||
|
{
|
||||||
|
userrole.ExpiryDate = DateTime.UtcNow;
|
||||||
|
await UserRoleService.UpdateUserRoleAsync(userrole);
|
||||||
|
await logger.LogInformation("User {Username} Expired From Role {Role}", userrole.User.Username, userrole.Role.Name);
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
await UserRoleService.DeleteUserRoleAsync(UserRoleId);
|
await UserRoleService.DeleteUserRoleAsync(UserRoleId);
|
||||||
await logger.LogInformation("User Removed From Role {UserRoleId}", UserRoleId);
|
await logger.LogInformation("User {Username} Removed From Role {Role}", userrole.User.Username, userrole.Role.Name);
|
||||||
|
}
|
||||||
AddModuleMessage(Localizer["Confirm.User.RoleRemoved"], MessageType.Success);
|
AddModuleMessage(Localizer["Confirm.User.RoleRemoved"], MessageType.Success);
|
||||||
await GetUserRoles();
|
await GetUserRoles();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
@ -198,9 +204,4 @@ else
|
|||||||
AddModuleMessage(Localizer["Error.User.RemoveRole"], MessageType.Error);
|
AddModuleMessage(Localizer["Error.User.RemoveRole"], MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
@inject IStringLocalizer<Index> Localizer
|
@inject IStringLocalizer<Index> Localizer
|
||||||
@inject INotificationService NotificationService
|
@inject INotificationService NotificationService
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
@inject IOutputCacheService CacheService
|
||||||
|
|
||||||
@if (_initialized)
|
@if (_initialized)
|
||||||
{
|
{
|
||||||
@ -50,11 +51,12 @@
|
|||||||
</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="sitemap" HelpText="The site map url for this site which can be submitted to search engines for indexing" ResourceKey="SiteMap">Site Map: </Label>
|
<Label Class="col-sm-3" For="sitemap" HelpText="The site map url for this site which can be submitted to search engines for indexing. The sitemap is cached for 5 minutes and the cache can be manually cleared." ResourceKey="SiteMap">Site Map: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input id="sitemap" class="form-control" @bind="@_sitemap" disabled />
|
<input id="sitemap" class="form-control" @bind="@_sitemap" disabled />
|
||||||
<a href="@_sitemap" class="btn btn-secondary" target="_new">@Localizer["Browse"]</a>
|
<a href="@_sitemap" class="btn btn-secondary" target="_new">@Localizer["Browse"]</a>
|
||||||
|
<button type="button" class="btn btn-danger" @onclick="EvictSitemapOutputCache">@Localizer["SiteMap.EvictCache"]</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -72,20 +74,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
<Section Name="Appearance" Heading="Appearance" ResourceKey="Appearance">
|
<Section Name="Theme" Heading="Theme" ResourceKey="Theme">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="logo" HelpText="Specify a logo for the site" ResourceKey="Logo">Logo: </Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<FileManager FileId="@_logofileid" Filter="@_imageFiles" @ref="_logofilemanager" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="favicon" HelpText="Specify a Favicon" ResourceKey="FavoriteIcon">Favicon: </Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<FileManager FileId="@_faviconfileid" Filter="ico,png,gif" @ref="_faviconfilemanager" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="defaultTheme" HelpText="Select the sites default theme" ResourceKey="DefaultTheme">Default Theme: </Label>
|
<Label Class="col-sm-3" For="defaultTheme" HelpText="Select the sites default theme" ResourceKey="DefaultTheme">Default Theme: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@ -126,6 +116,32 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="cookieconsent" HelpText="Specify if cookie consent is enabled on this site. Please note this option must be used in conjunction with a Theme which supports cookie consent." ResourceKey="CookieConsent">Cookie Consent: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="cookieconsent" class="form-select" @bind="@_cookieconsent">
|
||||||
|
<option value="">@SharedLocalizer["Disabled"]</option>
|
||||||
|
<option value="optin">@Localizer["OptIn"]</option>
|
||||||
|
<option value="optout">@Localizer["OptOut"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
<Section Name="Appearance" Heading="Appearance" ResourceKey="Appearance">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="logo" HelpText="Specify a logo for the site" ResourceKey="Logo">Logo: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<FileManager FileId="@_logofileid" Filter="@_imageFiles" @ref="_logofilemanager" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="favicon" HelpText="Specify a Favicon" ResourceKey="FavoriteIcon">Favicon: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<FileManager FileId="@_faviconfileid" Filter="ico,png,gif" @ref="_faviconfilemanager" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Section>
|
</Section>
|
||||||
<Section Name="Functionality" Heading="Functionality" ResourceKey="Functionality">
|
<Section Name="Functionality" Heading="Functionality" ResourceKey="Functionality">
|
||||||
@ -144,18 +160,6 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="imageExt" HelpText="Enter a comma separated list of image file extensions" ResourceKey="ImageExtensions">Image Extensions: </Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<input id="imageExt" spellcheck="false" class="form-control" @bind="@_imageFiles" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row mb-1 align-items-center">
|
|
||||||
<Label Class="col-sm-3" For="uploadableFileExt" HelpText="Enter a comma separated list of uploadable file extensions" ResourceKey="UploadableFileExtensions">Uploadable File Extensions: </Label>
|
|
||||||
<div class="col-sm-9">
|
|
||||||
<input id="uploadableFileExt" spellcheck="false" class="form-control" @bind="@_uploadableFiles" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</Section>
|
</Section>
|
||||||
<Section Name="PageContent" Heading="Page Content" ResourceKey="PageContent">
|
<Section Name="PageContent" Heading="Page Content" ResourceKey="PageContent">
|
||||||
@ -427,11 +431,11 @@
|
|||||||
private string _themetype = "";
|
private string _themetype = "";
|
||||||
private string _containertype = "";
|
private string _containertype = "";
|
||||||
private string _admincontainertype = "";
|
private string _admincontainertype = "";
|
||||||
|
private string _cookieconsent = "";
|
||||||
|
|
||||||
private Dictionary<string, string> _textEditors = new Dictionary<string, string>();
|
private Dictionary<string, string> _textEditors = new Dictionary<string, string>();
|
||||||
private string _textEditor = "";
|
private string _textEditor = "";
|
||||||
private string _imageFiles = string.Empty;
|
private string _imageFiles = string.Empty;
|
||||||
private string _uploadableFiles = string.Empty;
|
|
||||||
|
|
||||||
private string _headcontent = string.Empty;
|
private string _headcontent = string.Empty;
|
||||||
private string _bodycontent = string.Empty;
|
private string _bodycontent = string.Empty;
|
||||||
@ -518,6 +522,7 @@
|
|||||||
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
|
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, _themetype);
|
||||||
_containertype = (!string.IsNullOrEmpty(site.DefaultContainerType)) ? site.DefaultContainerType : Constants.DefaultContainer;
|
_containertype = (!string.IsNullOrEmpty(site.DefaultContainerType)) ? site.DefaultContainerType : Constants.DefaultContainer;
|
||||||
_admincontainertype = (!string.IsNullOrEmpty(site.AdminContainerType)) ? site.AdminContainerType : Constants.DefaultAdminContainer;
|
_admincontainertype = (!string.IsNullOrEmpty(site.AdminContainerType)) ? site.AdminContainerType : Constants.DefaultAdminContainer;
|
||||||
|
_cookieconsent = SettingService.GetSetting(settings, "CookieConsent", string.Empty);
|
||||||
|
|
||||||
// functionality
|
// functionality
|
||||||
var textEditors = ServiceProvider.GetServices<ITextEditor>();
|
var textEditors = ServiceProvider.GetServices<ITextEditor>();
|
||||||
@ -528,8 +533,6 @@
|
|||||||
_textEditor = SettingService.GetSetting(settings, "TextEditor", Constants.DefaultTextEditor);
|
_textEditor = SettingService.GetSetting(settings, "TextEditor", Constants.DefaultTextEditor);
|
||||||
_imageFiles = SettingService.GetSetting(settings, "ImageFiles", Constants.ImageFiles);
|
_imageFiles = SettingService.GetSetting(settings, "ImageFiles", Constants.ImageFiles);
|
||||||
_imageFiles = (string.IsNullOrEmpty(_imageFiles)) ? Constants.ImageFiles : _imageFiles;
|
_imageFiles = (string.IsNullOrEmpty(_imageFiles)) ? Constants.ImageFiles : _imageFiles;
|
||||||
_uploadableFiles = SettingService.GetSetting(settings, "UploadableFiles", Constants.UploadableFiles);
|
|
||||||
_uploadableFiles = (string.IsNullOrEmpty(_uploadableFiles)) ? Constants.UploadableFiles : _uploadableFiles;
|
|
||||||
|
|
||||||
// page content
|
// page content
|
||||||
_headcontent = site.HeadContent;
|
_headcontent = site.HeadContent;
|
||||||
@ -732,10 +735,11 @@
|
|||||||
settings = SettingService.SetSetting(settings, "SiteGuid", _siteguid, true);
|
settings = SettingService.SetSetting(settings, "SiteGuid", _siteguid, true);
|
||||||
settings = SettingService.SetSetting(settings, "NotificationRetention", _retention.ToString(), true);
|
settings = SettingService.SetSetting(settings, "NotificationRetention", _retention.ToString(), true);
|
||||||
|
|
||||||
|
//cookie consent
|
||||||
|
settings = SettingService.SetSetting(settings, "CookieConsent", _cookieconsent);
|
||||||
|
|
||||||
// functionality
|
// functionality
|
||||||
settings = SettingService.SetSetting(settings, "TextEditor", _textEditor);
|
settings = SettingService.SetSetting(settings, "TextEditor", _textEditor);
|
||||||
settings = SettingService.SetSetting(settings, "ImageFiles", (_imageFiles != Constants.ImageFiles) ? _imageFiles.Replace(" ", "") : "", false);
|
|
||||||
settings = SettingService.SetSetting(settings, "UploadableFiles", (_uploadableFiles != Constants.UploadableFiles) ? _uploadableFiles.Replace(" ", "") : "", false);
|
|
||||||
|
|
||||||
await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId);
|
await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId);
|
||||||
|
|
||||||
@ -930,4 +934,9 @@
|
|||||||
_aliasname = "";
|
_aliasname = "";
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task EvictSitemapOutputCache() {
|
||||||
|
await CacheService.EvictByTag(Constants.SitemapOutputCacheTag);
|
||||||
|
AddModuleMessage(Localizer["Success.SiteMap.CacheEvicted"], MessageType.Success);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -133,16 +133,30 @@
|
|||||||
</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="packageregistryurl" HelpText="Specify The Package Manager Service For Installing Modules, Themes, And Translations. If This Field Is Blank It Means The Package Manager Service Is Disabled For This Installation." ResourceKey="PackageManager">Package Manager: </Label>
|
<Label Class="col-sm-3" For="cachecontrol" HelpText="Provide a Cache-Control directive for static assets. For example 'public, max-age=60' indicates that static assets should be cached for 60 seconds. A blank value indicates caching is not enabled." ResourceKey="CacheControl">Static Asset Caching: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="cachecontrol" class="form-control" @bind="@_cachecontrol" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="packageregistryurl" HelpText="Specify The Url Of The Package Manager Service For Installing Modules, Themes, And Translations. If This Field Is Blank It Means The Package Manager Service Is Disabled For This Installation." ResourceKey="PackageManager">Package Manager Url: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<input id="packageregistryurl" class="form-control" @bind="@_packageregistryurl" />
|
<input id="packageregistryurl" class="form-control" @bind="@_packageregistryurl" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="packageregistryemail" HelpText="Specify The Email Address Of The User Account Used For Interacting With The Package Manager Service. This Account Is Used For Managing Packages Across Multiple Installations." ResourceKey="PackageManagerEmail">Package Manager Email: </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="packageregistryemail" class="form-control" @bind="@_packageregistryemail" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<br /><br />
|
<br /><br />
|
||||||
<button type="button" class="btn btn-success" @onclick="SaveConfig">@SharedLocalizer["Save"]</button>
|
<button type="button" class="btn btn-success" @onclick="SaveConfig">@SharedLocalizer["Save"]</button>
|
||||||
<a class="btn btn-primary" href="swagger/index.html" target="_new">@Localizer["Access.ApiFramework"]</a>
|
|
||||||
<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" />
|
||||||
|
<br /><br />
|
||||||
|
<a class="btn btn-primary" href="swagger/index.html" target="_new">@Localizer["Swagger"]</a>
|
||||||
|
<a class="btn btn-secondary" href="api/endpoint" target="_new">@Localizer["Endpoints"]</a>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel Name="Log" Heading="Log" ResourceKey="Log">
|
<TabPanel Name="Log" Heading="Log" ResourceKey="Log">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
@ -179,7 +193,9 @@
|
|||||||
private string _logginglevel = string.Empty;
|
private string _logginglevel = string.Empty;
|
||||||
private string _notificationlevel = string.Empty;
|
private string _notificationlevel = string.Empty;
|
||||||
private string _swagger = string.Empty;
|
private string _swagger = string.Empty;
|
||||||
|
private string _cachecontrol = string.Empty;
|
||||||
private string _packageregistryurl = string.Empty;
|
private string _packageregistryurl = string.Empty;
|
||||||
|
private string _packageregistryemail = string.Empty;
|
||||||
|
|
||||||
private string _log = string.Empty;
|
private string _log = string.Empty;
|
||||||
|
|
||||||
@ -210,7 +226,9 @@
|
|||||||
_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();
|
||||||
|
_cachecontrol = systeminfo["CacheControl"].ToString();
|
||||||
_packageregistryurl = systeminfo["PackageRegistryUrl"].ToString();
|
_packageregistryurl = systeminfo["PackageRegistryUrl"].ToString();
|
||||||
|
_packageregistryemail = systeminfo["PackageRegistryEmail"].ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
systeminfo = await SystemService.GetSystemInfoAsync("log");
|
systeminfo = await SystemService.GetSystemInfoAsync("log");
|
||||||
@ -229,7 +247,9 @@
|
|||||||
settings.Add("Logging:LogLevel:Default", _logginglevel);
|
settings.Add("Logging:LogLevel:Default", _logginglevel);
|
||||||
settings.Add("Logging:LogLevel:Notify", _notificationlevel);
|
settings.Add("Logging:LogLevel:Notify", _notificationlevel);
|
||||||
settings.Add("UseSwagger", _swagger);
|
settings.Add("UseSwagger", _swagger);
|
||||||
|
settings.Add("CacheControl", _cachecontrol);
|
||||||
settings.Add("PackageRegistryUrl", _packageregistryurl);
|
settings.Add("PackageRegistryUrl", _packageregistryurl);
|
||||||
|
settings.Add("PackageRegistryEmail", _packageregistryemail);
|
||||||
await SystemService.UpdateSystemInfoAsync(settings);
|
await SystemService.UpdateSystemInfoAsync(settings);
|
||||||
AddModuleMessage(Localizer["Success.UpdateConfig.Restart"], MessageType.Success);
|
AddModuleMessage(Localizer["Success.UpdateConfig.Restart"], MessageType.Success);
|
||||||
}
|
}
|
||||||
|
@ -45,24 +45,7 @@
|
|||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="packagename" HelpText="The unique name of the package from which this theme was installed. This value must be specified within the theme's ITheme interface specification." ResourceKey="PackageName">Package Name: </Label>
|
<Label Class="col-sm-3" For="packagename" HelpText="The unique name of the package from which this theme was installed. This value must be specified within the theme's ITheme interface specification." ResourceKey="PackageName">Package Name: </Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@if (!string.IsNullOrEmpty(_packagename))
|
|
||||||
{
|
|
||||||
<div class="input-group">
|
|
||||||
<input id="packagename" class="form-control" @bind="@_packagename" disabled />
|
<input id="packagename" class="form-control" @bind="@_packagename" disabled />
|
||||||
@if (string.IsNullOrEmpty(_packageurl))
|
|
||||||
{
|
|
||||||
<button type="button" class="btn btn-secondary" @onclick="ValidatePackage">@Localizer["Validate"]</button>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<a href="@_packageurl" target="_blank" class="btn btn-primary">@SharedLocalizer["Download"]</a>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<input id="packagename" class="form-control" @bind="@_packagename" disabled />
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@ -116,7 +99,6 @@
|
|||||||
private string _name;
|
private string _name;
|
||||||
private string _version;
|
private string _version;
|
||||||
private string _packagename = "";
|
private string _packagename = "";
|
||||||
private string _packageurl = "";
|
|
||||||
private string _owner = "";
|
private string _owner = "";
|
||||||
private string _url = "";
|
private string _url = "";
|
||||||
private string _contact = "";
|
private string _contact = "";
|
||||||
@ -185,27 +167,4 @@
|
|||||||
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task ValidatePackage()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var package = await PackageService.GetPackageAsync(_packagename, _version, true);
|
|
||||||
if (package == null || string.IsNullOrEmpty(package.PackageUrl))
|
|
||||||
{
|
|
||||||
AddModuleMessage(Localizer["Message.Validate"], MessageType.Warning);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_packageurl = package.PackageUrl;
|
|
||||||
AddModuleMessage(Localizer["Message.Download"], MessageType.Info);
|
|
||||||
}
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
await logger.LogError(ex, "Error Downloading Package {PackageId} {Version}", _packagename, _version);
|
|
||||||
AddModuleMessage(Localizer["Error.Validate"], MessageType.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject IThemeService ThemeService
|
@inject IThemeService ThemeService
|
||||||
@inject IPackageService PackageService
|
@inject IPackageService PackageService
|
||||||
|
@inject ISiteService SiteService
|
||||||
@inject IStringLocalizer<Index> Localizer
|
@inject IStringLocalizer<Index> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
@ -19,6 +20,7 @@ else
|
|||||||
|
|
||||||
<Pager Items="@_themes">
|
<Pager Items="@_themes">
|
||||||
<Header>
|
<Header>
|
||||||
|
<th style="width: 1px;"> </th>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
<th style="width: 1px;"> </th>
|
<th style="width: 1px;"> </th>
|
||||||
<th>@SharedLocalizer["Name"]</th>
|
<th>@SharedLocalizer["Name"]</th>
|
||||||
@ -36,6 +38,7 @@ else
|
|||||||
<ActionDialog Header="Delete Theme" Message="@string.Format(Localizer["Confirm.Theme.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await DeleteTheme(context))" ResourceKey="DeleteTheme" />
|
<ActionDialog Header="Delete Theme" Message="@string.Format(Localizer["Confirm.Theme.Delete"], context.Name)" Action="Delete" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await DeleteTheme(context))" ResourceKey="DeleteTheme" />
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
|
<td><NavLink class="btn btn-secondary" href="@NavigateUrl("admin/site")">@Localizer["Assign"]</NavLink></td>
|
||||||
<td>@context.Name</td>
|
<td>@context.Name</td>
|
||||||
<td>@context.Version</td>
|
<td>@context.Version</td>
|
||||||
<td>
|
<td>
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject IUrlMappingService UrlMappingService
|
@inject IUrlMappingService UrlMappingService
|
||||||
@inject ISiteService SiteService
|
@inject ISiteService SiteService
|
||||||
|
@inject ISettingService SettingService
|
||||||
@inject IStringLocalizer<Index> Localizer
|
@inject IStringLocalizer<Index> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
@ -62,6 +63,12 @@ else
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="retention" HelpText="Number of days of broken urls to retain" ResourceKey="Retention">Retention (Days): </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<input id="retention" class="form-control" type="number" min="0" step="1" @bind="@_retention" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
<button type="button" class="btn btn-success" @onclick="SaveSiteSettings">@SharedLocalizer["Save"]</button>
|
<button type="button" class="btn btn-success" @onclick="SaveSiteSettings">@SharedLocalizer["Save"]</button>
|
||||||
@ -73,6 +80,7 @@ else
|
|||||||
private bool _mapped = true;
|
private bool _mapped = true;
|
||||||
private List<UrlMapping> _urlMappings;
|
private List<UrlMapping> _urlMappings;
|
||||||
private string _capturebrokenurls;
|
private string _capturebrokenurls;
|
||||||
|
private int _retention = 30;
|
||||||
|
|
||||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
||||||
|
|
||||||
@ -80,6 +88,9 @@ else
|
|||||||
{
|
{
|
||||||
await GetUrlMappings();
|
await GetUrlMappings();
|
||||||
_capturebrokenurls = PageState.Site.CaptureBrokenUrls.ToString();
|
_capturebrokenurls = PageState.Site.CaptureBrokenUrls.ToString();
|
||||||
|
|
||||||
|
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||||
|
_retention = int.Parse(SettingService.GetSetting(settings, "UrlMappingRetention", "30"));
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void MappedChanged(ChangeEventArgs e)
|
private async void MappedChanged(ChangeEventArgs e)
|
||||||
@ -124,6 +135,11 @@ else
|
|||||||
var site = PageState.Site;
|
var site = PageState.Site;
|
||||||
site.CaptureBrokenUrls = bool.Parse(_capturebrokenurls);
|
site.CaptureBrokenUrls = bool.Parse(_capturebrokenurls);
|
||||||
await SiteService.UpdateSiteAsync(site);
|
await SiteService.UpdateSiteAsync(site);
|
||||||
|
|
||||||
|
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
|
||||||
|
settings = SettingService.SetSetting(settings, "UrlMappingRetention", _retention.ToString(), true);
|
||||||
|
await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId);
|
||||||
|
|
||||||
AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success);
|
AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
@inject IProfileService ProfileService
|
@inject IProfileService ProfileService
|
||||||
@inject ISettingService SettingService
|
@inject ISettingService SettingService
|
||||||
@inject IFileService FileService
|
@inject IFileService FileService
|
||||||
|
@inject IServiceProvider ServiceProvider
|
||||||
@inject IStringLocalizer<Edit> Localizer
|
@inject IStringLocalizer<Edit> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
@ -51,6 +52,8 @@
|
|||||||
<input id="displayname" class="form-control" @bind="@displayname" />
|
<input id="displayname" class="form-control" @bind="@displayname" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
|
{
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="isdeleted" HelpText="Indicate if the user is active" ResourceKey="IsDeleted"></Label>
|
<Label Class="col-sm-3" For="isdeleted" HelpText="Indicate if the user is active" ResourceKey="IsDeleted"></Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@ -60,6 +63,7 @@
|
|||||||
</select>
|
</select>
|
||||||
</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="lastlogin" HelpText="The date and time when the user last signed in" ResourceKey="LastLogin"></Label>
|
<Label Class="col-sm-3" For="lastlogin" HelpText="The date and time when the user last signed in" ResourceKey="LastLogin"></Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@ -127,8 +131,15 @@
|
|||||||
|
|
||||||
<button type="button" class="btn btn-success" @onclick="SaveUser">@SharedLocalizer["Save"]</button>
|
<button type="button" class="btn btn-success" @onclick="SaveUser">@SharedLocalizer["Save"]</button>
|
||||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||||
<br />
|
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin) && PageState.Runtime != Shared.Runtime.Hybrid && !ishost)
|
||||||
<br />
|
{
|
||||||
|
<button type="button" class="btn btn-primary ms-1" @onclick="ImpersonateUser">@Localizer["Impersonate"]</button>
|
||||||
|
}
|
||||||
|
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host) && isdeleted == "True")
|
||||||
|
{
|
||||||
|
<ActionDialog Header="Delete User" Message="Are You Sure You Wish To Permanently Delete This User?" Action="Delete" Security="SecurityAccessLevel.Host" Class="btn btn-danger" OnClick="@(async () => await DeleteUser())" ResourceKey="DeleteUser" />
|
||||||
|
}
|
||||||
|
<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>
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,6 +157,7 @@
|
|||||||
private string isdeleted;
|
private string isdeleted;
|
||||||
private string lastlogin;
|
private string lastlogin;
|
||||||
private string lastipaddress;
|
private string lastipaddress;
|
||||||
|
private bool ishost = false;
|
||||||
|
|
||||||
private List<Profile> profiles;
|
private List<Profile> profiles;
|
||||||
private Dictionary<string, string> userSettings;
|
private Dictionary<string, string> userSettings;
|
||||||
@ -180,6 +192,7 @@
|
|||||||
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;
|
||||||
|
ishost = UserSecurity.ContainsRole(user.Roles, RoleNames.Host);
|
||||||
|
|
||||||
userSettings = user.Settings;
|
userSettings = user.Settings;
|
||||||
createdby = user.CreatedBy;
|
createdby = user.CreatedBy;
|
||||||
@ -226,8 +239,10 @@
|
|||||||
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;
|
||||||
|
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
|
{
|
||||||
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);
|
||||||
if (user != null)
|
if (user != null)
|
||||||
@ -259,6 +274,44 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task ImpersonateUser()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await logger.LogInformation(LogFunction.Security, "User {Username} Impersonated By Administrator {Administrator}", username, PageState.User.Username);
|
||||||
|
|
||||||
|
// post back to the server so that the cookies are set correctly
|
||||||
|
var interop = new Interop(JSRuntime);
|
||||||
|
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, username = username, returnurl = PageState.Alias.Path };
|
||||||
|
string url = Utilities.TenantUrl(PageState.Alias, "/pages/impersonate/");
|
||||||
|
await interop.SubmitForm(url, fields);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Impersonating User {Username} {Error}", username, ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.User.Impersonate"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task DeleteUser()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host) && userid != PageState.User.UserId)
|
||||||
|
{
|
||||||
|
var user = await UserService.GetUserAsync(userid, PageState.Site.SiteId);
|
||||||
|
await UserService.DeleteUserAsync(user.UserId, PageState.Site.SiteId);
|
||||||
|
await logger.LogInformation("User Permanently Deleted {User}", user);
|
||||||
|
NavigationManager.NavigateTo(NavigateUrl());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await logger.LogError(ex, "Error Permanently Deleting User {UserId} {Error}", userid, ex.Message);
|
||||||
|
AddModuleMessage(Localizer["Error.DeleteUser"], MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private bool ValidateProfiles()
|
private bool ValidateProfiles()
|
||||||
{
|
{
|
||||||
foreach (Profile profile in profiles)
|
foreach (Profile profile in profiles)
|
||||||
|
@ -35,7 +35,7 @@ else
|
|||||||
<ActionLink Action="Edit" Text="Edit" Parameters="@($"id=" + context.UserId.ToString())" Security="SecurityAccessLevel.Edit" ResourceKey="EditUser" />
|
<ActionLink Action="Edit" Text="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.Edit" 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 || context.User.IsDeleted)" ResourceKey="DeleteUser" />
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<ActionLink Action="Roles" Text="Roles" Parameters="@($"id=" + context.UserId.ToString())" Security="SecurityAccessLevel.Edit" ResourceKey="Roles" />
|
<ActionLink Action="Roles" Text="Roles" Parameters="@($"id=" + context.UserId.ToString())" Security="SecurityAccessLevel.Edit" ResourceKey="Roles" />
|
||||||
@ -54,11 +54,13 @@ else
|
|||||||
<Label Class="col-sm-3" For="allowregistration" HelpText="Do you want anonymous visitors to be able to register for an account on the site" ResourceKey="AllowRegistration">Allow User Registration?</Label>
|
<Label Class="col-sm-3" For="allowregistration" HelpText="Do you want anonymous visitors to be able to register for an account on the site" ResourceKey="AllowRegistration">Allow User Registration?</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
<select id="allowregistration" class="form-select" @bind="@_allowregistration">
|
<select id="allowregistration" class="form-select" @bind="@_allowregistration">
|
||||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||||
<option value="False">@SharedLocalizer["No"]</option>
|
<option value="false">@SharedLocalizer["No"]</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
|
{
|
||||||
@if (_providertype != "")
|
@if (_providertype != "")
|
||||||
{
|
{
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
@ -80,8 +82,6 @@ else
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
|
||||||
{
|
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="twofactor" HelpText="Do you want users to use two factor authentication? Note that you should use the Disabled option until you have successfully verified that the Notification Job in Scheduled Jobs is enabled and your SMTP options in Site Settings are configured or else you will lock yourself out." ResourceKey="TwoFactor">Two Factor?</Label>
|
<Label Class="col-sm-3" For="twofactor" HelpText="Do you want users to use two factor authentication? Note that you should use the Disabled option until you have successfully verified that the Notification Job in Scheduled Jobs is enabled and your SMTP options in Site Settings are configured or else you will lock yourself out." ResourceKey="TwoFactor">Two Factor?</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@ -113,6 +113,15 @@ else
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="logouteverywhere" HelpText="Do you want users to be logged out of every active session on any device, or only their current session?" ResourceKey="LogoutEverywhere">Logout Everywhere?</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="logouteverywhere" class="form-select" @bind="@_logouteverywhere">
|
||||||
|
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="false">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
</Section>
|
</Section>
|
||||||
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
@ -379,6 +388,15 @@ else
|
|||||||
<input id="profileclaimtypes" class="form-control" @bind="@_profileclaimtypes" />
|
<input id="profileclaimtypes" class="form-control" @bind="@_profileclaimtypes" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="savetokens" HelpText="Specify whether access and refresh tokens should be saved after a successful login. The default is false to reduce the size of the authentication cookie." ResourceKey="SaveTokens">Save Tokens?</Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="savetokens" class="form-select" @bind="@_savetokens" required>
|
||||||
|
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="false">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="row mb-1 align-items-center">
|
<div class="row mb-1 align-items-center">
|
||||||
<Label Class="col-sm-3" For="domainfilter" HelpText="Provide any email domain filter criteria (separated by commas). Domains to exclude should be prefixed with an exclamation point (!). For example 'microsoft.com,!hotmail.com' would include microsoft.com email addresses but not hotmail.com email addresses." ResourceKey="DomainFilter">Domain Filter:</Label>
|
<Label Class="col-sm-3" For="domainfilter" HelpText="Provide any email domain filter criteria (separated by commas). Domains to exclude should be prefixed with an exclamation point (!). For example 'microsoft.com,!hotmail.com' would include microsoft.com email addresses but not hotmail.com email addresses." ResourceKey="DomainFilter">Domain Filter:</Label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-9">
|
||||||
@ -460,6 +478,7 @@ else
|
|||||||
private string _cookiename;
|
private string _cookiename;
|
||||||
private string _cookieexpiration;
|
private string _cookieexpiration;
|
||||||
private string _alwaysremember;
|
private string _alwaysremember;
|
||||||
|
private string _logouteverywhere;
|
||||||
|
|
||||||
private string _minimumlength;
|
private string _minimumlength;
|
||||||
private string _uniquecharacters;
|
private string _uniquecharacters;
|
||||||
@ -497,6 +516,7 @@ else
|
|||||||
private string _roleclaimmappings;
|
private string _roleclaimmappings;
|
||||||
private string _synchronizeroles;
|
private string _synchronizeroles;
|
||||||
private string _profileclaimtypes;
|
private string _profileclaimtypes;
|
||||||
|
private string _savetokens;
|
||||||
private string _domainfilter;
|
private string _domainfilter;
|
||||||
private string _createusers;
|
private string _createusers;
|
||||||
private string _verifyusers;
|
private string _verifyusers;
|
||||||
@ -519,7 +539,7 @@ else
|
|||||||
await LoadUsersAsync(true);
|
await LoadUsersAsync(true);
|
||||||
|
|
||||||
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||||
_allowregistration = PageState.Site.AllowRegistration.ToString();
|
_allowregistration = PageState.Site.AllowRegistration.ToString().ToLower();
|
||||||
_allowsitelogin = SettingService.GetSetting(settings, "LoginOptions:AllowSiteLogin", "true");
|
_allowsitelogin = SettingService.GetSetting(settings, "LoginOptions:AllowSiteLogin", "true");
|
||||||
|
|
||||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
@ -528,6 +548,7 @@ else
|
|||||||
_cookiename = SettingService.GetSetting(settings, "LoginOptions:CookieName", ".AspNetCore.Identity.Application");
|
_cookiename = SettingService.GetSetting(settings, "LoginOptions:CookieName", ".AspNetCore.Identity.Application");
|
||||||
_cookieexpiration = SettingService.GetSetting(settings, "LoginOptions:CookieExpiration", "");
|
_cookieexpiration = SettingService.GetSetting(settings, "LoginOptions:CookieExpiration", "");
|
||||||
_alwaysremember = SettingService.GetSetting(settings, "LoginOptions:AlwaysRemember", "false");
|
_alwaysremember = SettingService.GetSetting(settings, "LoginOptions:AlwaysRemember", "false");
|
||||||
|
_logouteverywhere = SettingService.GetSetting(settings, "LoginOptions:LogoutEverywhere", "false");
|
||||||
|
|
||||||
_minimumlength = SettingService.GetSetting(settings, "IdentityOptions:Password:RequiredLength", "6");
|
_minimumlength = SettingService.GetSetting(settings, "IdentityOptions:Password:RequiredLength", "6");
|
||||||
_uniquecharacters = SettingService.GetSetting(settings, "IdentityOptions:Password:RequiredUniqueChars", "1");
|
_uniquecharacters = SettingService.GetSetting(settings, "IdentityOptions:Password:RequiredUniqueChars", "1");
|
||||||
@ -577,6 +598,7 @@ else
|
|||||||
_roleclaimmappings = SettingService.GetSetting(settings, "ExternalLogin:RoleClaimMappings", "");
|
_roleclaimmappings = SettingService.GetSetting(settings, "ExternalLogin:RoleClaimMappings", "");
|
||||||
_synchronizeroles = SettingService.GetSetting(settings, "ExternalLogin:SynchronizeRoles", "false");
|
_synchronizeroles = SettingService.GetSetting(settings, "ExternalLogin:SynchronizeRoles", "false");
|
||||||
_profileclaimtypes = SettingService.GetSetting(settings, "ExternalLogin:ProfileClaimTypes", "");
|
_profileclaimtypes = SettingService.GetSetting(settings, "ExternalLogin:ProfileClaimTypes", "");
|
||||||
|
_savetokens = SettingService.GetSetting(settings, "ExternalLogin:SaveTokens", "false");
|
||||||
_domainfilter = SettingService.GetSetting(settings, "ExternalLogin:DomainFilter", "");
|
_domainfilter = SettingService.GetSetting(settings, "ExternalLogin:DomainFilter", "");
|
||||||
_createusers = SettingService.GetSetting(settings, "ExternalLogin:CreateUsers", "true");
|
_createusers = SettingService.GetSetting(settings, "ExternalLogin:CreateUsers", "true");
|
||||||
_verifyusers = SettingService.GetSetting(settings, "ExternalLogin:VerifyUsers", "true");
|
_verifyusers = SettingService.GetSetting(settings, "ExternalLogin:VerifyUsers", "true");
|
||||||
@ -599,20 +621,32 @@ else
|
|||||||
private async Task DeleteUser(UserRole UserRole)
|
private async Task DeleteUser(UserRole UserRole)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
{
|
||||||
|
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||||
{
|
{
|
||||||
var user = await UserService.GetUserAsync(UserRole.UserId, PageState.Site.SiteId);
|
var user = await UserService.GetUserAsync(UserRole.UserId, PageState.Site.SiteId);
|
||||||
if (user != null)
|
if (user != null)
|
||||||
{
|
{
|
||||||
await UserService.DeleteUserAsync(user.UserId, PageState.Site.SiteId);
|
user.IsDeleted = true;
|
||||||
await logger.LogInformation("User Deleted {User}", UserRole.User);
|
await UserService.UpdateUserAsync(user);
|
||||||
|
await logger.LogInformation("User Soft Deleted {User}", user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var userrole = await UserRoleService.GetUserRoleAsync(UserRole.UserRoleId);
|
||||||
|
userrole.ExpiryDate = DateTime.UtcNow;
|
||||||
|
await UserRoleService.UpdateUserRoleAsync(userrole);
|
||||||
|
await logger.LogInformation("User {Username} Expired From Role {Role}", userrole.User.Username, userrole.Role.Name);
|
||||||
|
}
|
||||||
|
AddModuleMessage(Localizer["Success.DeleteUser"], MessageType.Success);
|
||||||
await LoadUsersAsync(true);
|
await LoadUsersAsync(true);
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
await logger.LogError(ex, "Error Deleting User {User} {Error}", UserRole.User, ex.Message);
|
await logger.LogError(ex, "Error Deleting User {User} {Error}", UserRole.User, ex.Message);
|
||||||
AddModuleMessage(ex.Message, MessageType.Error);
|
AddModuleMessage(Localizer["Error.DeleteUser"], MessageType.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -633,6 +667,7 @@ else
|
|||||||
settings = SettingService.SetSetting(settings, "LoginOptions:CookieName", _cookiename, true);
|
settings = SettingService.SetSetting(settings, "LoginOptions:CookieName", _cookiename, true);
|
||||||
settings = SettingService.SetSetting(settings, "LoginOptions:CookieExpiration", _cookieexpiration, true);
|
settings = SettingService.SetSetting(settings, "LoginOptions:CookieExpiration", _cookieexpiration, true);
|
||||||
settings = SettingService.SetSetting(settings, "LoginOptions:AlwaysRemember", _alwaysremember, false);
|
settings = SettingService.SetSetting(settings, "LoginOptions:AlwaysRemember", _alwaysremember, false);
|
||||||
|
settings = SettingService.SetSetting(settings, "LoginOptions:LogoutEverywhere", _logouteverywhere, false);
|
||||||
|
|
||||||
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequiredLength", _minimumlength, true);
|
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequiredLength", _minimumlength, true);
|
||||||
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequiredUniqueChars", _uniquecharacters, true);
|
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequiredUniqueChars", _uniquecharacters, true);
|
||||||
@ -666,6 +701,7 @@ else
|
|||||||
settings = SettingService.SetSetting(settings, "ExternalLogin:RoleClaimMappings", _roleclaimmappings, true);
|
settings = SettingService.SetSetting(settings, "ExternalLogin:RoleClaimMappings", _roleclaimmappings, true);
|
||||||
settings = SettingService.SetSetting(settings, "ExternalLogin:SynchronizeRoles", _synchronizeroles, true);
|
settings = SettingService.SetSetting(settings, "ExternalLogin:SynchronizeRoles", _synchronizeroles, true);
|
||||||
settings = SettingService.SetSetting(settings, "ExternalLogin:ProfileClaimTypes", _profileclaimtypes, true);
|
settings = SettingService.SetSetting(settings, "ExternalLogin:ProfileClaimTypes", _profileclaimtypes, true);
|
||||||
|
settings = SettingService.SetSetting(settings, "ExternalLogin:SaveTokens", _savetokens, true);
|
||||||
settings = SettingService.SetSetting(settings, "ExternalLogin:DomainFilter", _domainfilter, true);
|
settings = SettingService.SetSetting(settings, "ExternalLogin:DomainFilter", _domainfilter, true);
|
||||||
settings = SettingService.SetSetting(settings, "ExternalLogin:CreateUsers", _createusers, true);
|
settings = SettingService.SetSetting(settings, "ExternalLogin:CreateUsers", _createusers, true);
|
||||||
settings = SettingService.SetSetting(settings, "ExternalLogin:VerifyUsers", _verifyusers, true);
|
settings = SettingService.SetSetting(settings, "ExternalLogin:VerifyUsers", _verifyusers, true);
|
||||||
|
@ -63,7 +63,7 @@ else
|
|||||||
<td>@Utilities.UtcAsLocalDate(context.EffectiveDate)</td>
|
<td>@Utilities.UtcAsLocalDate(context.EffectiveDate)</td>
|
||||||
<td>@Utilities.UtcAsLocalDate(context.ExpiryDate)</td>
|
<td>@Utilities.UtcAsLocalDate(context.ExpiryDate)</td>
|
||||||
<td>
|
<td>
|
||||||
<ActionDialog Header="Remove Role" Message="@string.Format(Localizer["Confirm.User.RemoveRole"], context.Role.Name)" Action="Delete" Security="SecurityAccessLevel.Edit" Class="btn btn-danger" OnClick="@(async () => await DeleteUserRole(context.UserRoleId))" Disabled="@(context.Role.IsAutoAssigned || (context.Role.Name == RoleNames.Host && userid == PageState.User.UserId))" ResourceKey="DeleteUserRole" />
|
<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.Name == RoleNames.Host && userid == PageState.User.UserId)" ResourceKey="DeleteUserRole" />
|
||||||
</td>
|
</td>
|
||||||
</Row>
|
</Row>
|
||||||
</Pager>
|
</Pager>
|
||||||
@ -170,9 +170,19 @@ else
|
|||||||
private async Task DeleteUserRole(int UserRoleId)
|
private async Task DeleteUserRole(int UserRoleId)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
{
|
||||||
|
var userrole = await UserRoleService.GetUserRoleAsync(UserRoleId);
|
||||||
|
if (userrole.Role.Name == RoleNames.Registered)
|
||||||
|
{
|
||||||
|
userrole.ExpiryDate = DateTime.UtcNow;
|
||||||
|
await UserRoleService.UpdateUserRoleAsync(userrole);
|
||||||
|
await logger.LogInformation("User {Username} Expired From Role {Role}", userrole.User.Username, userrole.Role.Name);
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
await UserRoleService.DeleteUserRoleAsync(UserRoleId);
|
await UserRoleService.DeleteUserRoleAsync(UserRoleId);
|
||||||
await logger.LogInformation("User Removed From Role {UserRoleId}", UserRoleId);
|
await logger.LogInformation("User {Username} Removed From Role {Role}", userrole.User.Username, userrole.Role.Name);
|
||||||
|
}
|
||||||
AddModuleMessage(Localizer["Success.User.Remove"], MessageType.Success);
|
AddModuleMessage(Localizer["Success.User.Remove"], MessageType.Success);
|
||||||
await GetUserRoles();
|
await GetUserRoles();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
|
@ -22,9 +22,9 @@
|
|||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
@if (!string.IsNullOrEmpty(Action))
|
@if (!string.IsNullOrEmpty(Action))
|
||||||
{
|
{
|
||||||
<button type="button" class="@Class" @onclick="Confirm">@((MarkupString)_iconSpan) @Text</button>
|
<button type="button" class="@ConfirmClass" @onclick="Confirm">@((MarkupString)_iconSpan) @Text</button>
|
||||||
}
|
}
|
||||||
<button type="button" class="btn btn-secondary" @onclick="DisplayModal">@SharedLocalizer["Cancel"]</button>
|
<button type="button" class="@CancelClass" @onclick="DisplayModal">@SharedLocalizer["Cancel"]</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -66,12 +66,12 @@ else
|
|||||||
{
|
{
|
||||||
<form method="post" @formname="@($"ActionDialogConfirmForm:{ModuleState.PageModuleId}:{Id}")" @onsubmit="Confirm" data-enhance>
|
<form method="post" @formname="@($"ActionDialogConfirmForm:{ModuleState.PageModuleId}:{Id}")" @onsubmit="Confirm" data-enhance>
|
||||||
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||||
<button type="submit" class="@Class">@((MarkupString)_iconSpan) @Text</button>
|
<button type="submit" class="@ConfirmClass">@((MarkupString)_iconSpan) @Text</button>
|
||||||
</form>
|
</form>
|
||||||
}
|
}
|
||||||
<form method="post" @formname="@($"ActionDialogCancelForm:{ModuleState.PageModuleId}:{Id}")" @onsubmit="DisplayModal" data-enhance>
|
<form method="post" @formname="@($"ActionDialogCancelForm:{ModuleState.PageModuleId}:{Id}")" @onsubmit="DisplayModal" data-enhance>
|
||||||
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||||
<button type="submit" class="btn btn-secondary">@SharedLocalizer["Cancel"]</button>
|
<button type="submit" class="@CancelClass">@SharedLocalizer["Cancel"]</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -128,6 +128,12 @@ else
|
|||||||
[Parameter]
|
[Parameter]
|
||||||
public string Class { get; set; } // optional
|
public string Class { get; set; } // optional
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string ConfirmClass { get; set; } // optional - for Confirm modal button
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public string CancelClass { get; set; } // optional - for Cancel modal button
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public bool Disabled { get; set; } // optional
|
public bool Disabled { get; set; } // optional
|
||||||
|
|
||||||
@ -168,6 +174,16 @@ else
|
|||||||
Class = "btn btn-success";
|
Class = "btn btn-success";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(ConfirmClass))
|
||||||
|
{
|
||||||
|
ConfirmClass = Class;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(CancelClass))
|
||||||
|
{
|
||||||
|
CancelClass = "btn btn-secondary";
|
||||||
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(EditMode))
|
if (!string.IsNullOrEmpty(EditMode))
|
||||||
{
|
{
|
||||||
_editmode = bool.Parse(EditMode);
|
_editmode = bool.Parse(EditMode);
|
||||||
|
@ -3,8 +3,8 @@
|
|||||||
@inherits ModuleControlBase
|
@inherits ModuleControlBase
|
||||||
@inject IFolderService FolderService
|
@inject IFolderService FolderService
|
||||||
@inject IFileService FileService
|
@inject IFileService FileService
|
||||||
@inject ISettingService SettingService
|
|
||||||
@inject IUserService UserService
|
@inject IUserService UserService
|
||||||
|
@inject ISettingService SettingService
|
||||||
@inject IStringLocalizer<FileManager> Localizer
|
@inject IStringLocalizer<FileManager> Localizer
|
||||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
@ -157,6 +157,9 @@
|
|||||||
[Parameter]
|
[Parameter]
|
||||||
public bool UploadMultiple { get; set; } = false; // optional - enable multiple file uploads - default false
|
public bool UploadMultiple { get; set; } = false; // optional - enable multiple file uploads - default false
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public int ChunkSize { get; set; } = 1; // optional - size of file chunks to upload in MB
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public EventCallback<int> OnUpload { get; set; } // optional - executes a method in the calling component when a file is uploaded
|
public EventCallback<int> OnUpload { get; set; } // optional - executes a method in the calling component when a file is uploaded
|
||||||
|
|
||||||
@ -359,6 +362,8 @@
|
|||||||
}
|
}
|
||||||
if (restricted == "")
|
if (restricted == "")
|
||||||
{
|
{
|
||||||
|
CancellationTokenSource tokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// upload the files
|
// upload the files
|
||||||
@ -377,57 +382,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var chunksize = ChunkSize;
|
||||||
|
if (chunksize == 1)
|
||||||
|
{
|
||||||
|
// if ChunkSize parameter is not overridden use the site setting
|
||||||
|
chunksize = int.Parse(SettingService.GetSetting(PageState.Site.Settings, "MaxChunkSize", "1"));
|
||||||
|
}
|
||||||
|
|
||||||
if (!ShowProgress)
|
if (!ShowProgress)
|
||||||
{
|
{
|
||||||
_uploading = true;
|
_uploading = true;
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
await interop.UploadFiles(posturl, folder, _guid, SiteState.AntiForgeryToken, jwt);
|
// upload files
|
||||||
|
var success = await interop.UploadFiles(posturl, folder, _guid, SiteState.AntiForgeryToken, jwt, chunksize, tokenSource.Token);
|
||||||
// uploading is asynchronous so we need to poll to determine if uploads are completed
|
|
||||||
var success = true;
|
|
||||||
int upload = 0;
|
|
||||||
while (upload < uploads.Length && success)
|
|
||||||
{
|
|
||||||
success = false;
|
|
||||||
var filename = uploads[upload].Split(':')[0];
|
|
||||||
|
|
||||||
var size = Int64.Parse(uploads[upload].Split(':')[1]); // bytes
|
|
||||||
var megabits = (size / 1048576.0) * 8; // binary conversion
|
|
||||||
var uploadspeed = (PageState.Alias.Name.Contains("localhost")) ? 100 : 3; // 3 Mbps is FCC minimum for broadband upload
|
|
||||||
var uploadtime = (megabits / uploadspeed); // seconds
|
|
||||||
var maxattempts = 5; // polling (minimum timeout duration will be 5 seconds)
|
|
||||||
var sleep = (int)Math.Ceiling(uploadtime / maxattempts) * 1000; // milliseconds
|
|
||||||
|
|
||||||
int attempts = 0;
|
|
||||||
while (attempts < maxattempts && !success)
|
|
||||||
{
|
|
||||||
attempts += 1;
|
|
||||||
Thread.Sleep(sleep);
|
|
||||||
|
|
||||||
if (Folder == Constants.PackagesFolder)
|
|
||||||
{
|
|
||||||
var files = await FileService.GetFilesAsync(folder);
|
|
||||||
if (files != null && files.Any(item => item.Name == filename))
|
|
||||||
{
|
|
||||||
success = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var file = await FileService.GetFileAsync(int.Parse(folder), filename);
|
|
||||||
if (file != null)
|
|
||||||
{
|
|
||||||
success = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (success)
|
|
||||||
{
|
|
||||||
upload++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// reset progress indicators
|
// reset progress indicators
|
||||||
if (ShowProgress)
|
if (ShowProgress)
|
||||||
@ -452,7 +421,7 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
await logger.LogInformation("File Upload Failed Or Is Still In Progress {Files}", uploads);
|
await logger.LogError("File Upload Failed {Files}", uploads);
|
||||||
_message = Localizer["Error.File.Upload"];
|
_message = Localizer["Error.File.Upload"];
|
||||||
_messagetype = MessageType.Error;
|
_messagetype = MessageType.Error;
|
||||||
}
|
}
|
||||||
@ -482,6 +451,10 @@
|
|||||||
_message = Localizer["Error.File.Upload"];
|
_message = Localizer["Error.File.Upload"];
|
||||||
_messagetype = MessageType.Error;
|
_messagetype = MessageType.Error;
|
||||||
_uploading = false;
|
_uploading = false;
|
||||||
|
await tokenSource.CancelAsync();
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
tokenSource.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
@namespace Oqtane.Modules.HtmlText
|
@namespace Oqtane.Modules.HtmlText
|
||||||
@inherits ModuleBase
|
@inherits ModuleBase
|
||||||
@inject IHtmlTextService HtmlTextService
|
@inject IHtmlTextService HtmlTextService
|
||||||
|
@inject ISettingService SettingService
|
||||||
@inject IStringLocalizer<Index> Localizer
|
@inject IStringLocalizer<Index> Localizer
|
||||||
|
|
||||||
@if (PageState.EditMode)
|
@if (PageState.EditMode)
|
||||||
@ -36,6 +37,10 @@
|
|||||||
{
|
{
|
||||||
content = htmltext.Content;
|
content = htmltext.Content;
|
||||||
content = Utilities.FormatContent(content, PageState.Alias, "render");
|
content = Utilities.FormatContent(content, PageState.Alias, "render");
|
||||||
|
if (bool.Parse(SettingService.GetSetting(ModuleState.Settings, "DynamicTokens", "false")))
|
||||||
|
{
|
||||||
|
content = ReplaceTokens(content);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -15,7 +15,7 @@ namespace Oqtane.Modules.HtmlText
|
|||||||
Version = "1.0.1",
|
Version = "1.0.1",
|
||||||
ServerManagerType = "Oqtane.Modules.HtmlText.Manager.HtmlTextManager, Oqtane.Server",
|
ServerManagerType = "Oqtane.Modules.HtmlText.Manager.HtmlTextManager, Oqtane.Server",
|
||||||
ReleaseVersions = "1.0.0,1.0.1",
|
ReleaseVersions = "1.0.0,1.0.1",
|
||||||
SettingsType = string.Empty,
|
SettingsType = "Oqtane.Modules.HtmlText.Settings, Oqtane.Client",
|
||||||
Resources = new List<Resource>()
|
Resources = new List<Resource>()
|
||||||
{
|
{
|
||||||
new Resource { ResourceType = ResourceType.Stylesheet, Url = "~/Module.css" }
|
new Resource { ResourceType = ResourceType.Stylesheet, Url = "~/Module.css" }
|
||||||
|
55
Oqtane.Client/Modules/HtmlText/Settings.razor
Normal file
55
Oqtane.Client/Modules/HtmlText/Settings.razor
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
@namespace Oqtane.Modules.HtmlText
|
||||||
|
@inherits ModuleBase
|
||||||
|
@inject ISettingService SettingService
|
||||||
|
@implements Oqtane.Interfaces.ISettingsControl
|
||||||
|
@inject IStringLocalizer<Settings> Localizer
|
||||||
|
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||||
|
|
||||||
|
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mb-1 align-items-center">
|
||||||
|
<Label Class="col-sm-3" For="dynamictokens" ResourceKey="DynamicTokens" ResourceType="@resourceType" HelpText="Do you wish to allow tokens to be dynamically replaced? Please note that this will affect the performance of your site.">Dynamic Tokens? </Label>
|
||||||
|
<div class="col-sm-9">
|
||||||
|
<select id="dynamictokens" class="form-select" @bind="@_dynamictokens">
|
||||||
|
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||||
|
<option value="false">@SharedLocalizer["No"]</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private string resourceType = "Oqtane.Modules.HtmlText.Settings, Oqtane.Client"; // for localization
|
||||||
|
|
||||||
|
private ElementReference form;
|
||||||
|
private bool validated = false;
|
||||||
|
|
||||||
|
private string _dynamictokens;
|
||||||
|
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_dynamictokens = SettingService.GetSetting(ModuleState.Settings, "DynamicTokens", "false");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
AddModuleMessage(ex.Message, MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task UpdateSettings()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId);
|
||||||
|
settings = SettingService.SetSetting(settings, "DynamicTokens", _dynamictokens);
|
||||||
|
await SettingService.UpdateModuleSettingsAsync(settings, ModuleState.ModuleId);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
AddModuleMessage(ex.Message, MessageType.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -10,6 +10,7 @@ using System.Collections.Generic;
|
|||||||
using Microsoft.JSInterop;
|
using Microsoft.JSInterop;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Dynamic;
|
using System.Dynamic;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
namespace Oqtane.Modules
|
namespace Oqtane.Modules
|
||||||
{
|
{
|
||||||
@ -18,6 +19,7 @@ namespace Oqtane.Modules
|
|||||||
private Logger _logger;
|
private Logger _logger;
|
||||||
private string _urlparametersstate;
|
private string _urlparametersstate;
|
||||||
private Dictionary<string, string> _urlparameters;
|
private Dictionary<string, string> _urlparameters;
|
||||||
|
private bool _scriptsloaded = false;
|
||||||
|
|
||||||
protected Logger logger => _logger ?? (_logger = new Logger(this));
|
protected Logger logger => _logger ?? (_logger = new Logger(this));
|
||||||
|
|
||||||
@ -34,7 +36,7 @@ namespace Oqtane.Modules
|
|||||||
protected PageState PageState { get; set; }
|
protected PageState PageState { get; set; }
|
||||||
|
|
||||||
[CascadingParameter]
|
[CascadingParameter]
|
||||||
protected Module ModuleState { get; set; }
|
protected Models.Module ModuleState { get; set; }
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public RenderModeBoundary RenderModeBoundary { get; set; }
|
public RenderModeBoundary RenderModeBoundary { get; set; }
|
||||||
@ -98,7 +100,7 @@ namespace Oqtane.Modules
|
|||||||
var inline = 0;
|
var inline = 0;
|
||||||
foreach (Resource resource in resources)
|
foreach (Resource resource in resources)
|
||||||
{
|
{
|
||||||
if ((string.IsNullOrEmpty(resource.RenderMode) || resource.RenderMode == RenderModes.Interactive) && !resource.Reload)
|
if (string.IsNullOrEmpty(resource.RenderMode) || resource.RenderMode == RenderModes.Interactive)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(resource.Url))
|
if (!string.IsNullOrEmpty(resource.Url))
|
||||||
{
|
{
|
||||||
@ -117,6 +119,7 @@ namespace Oqtane.Modules
|
|||||||
await interop.IncludeScripts(scripts.ToArray());
|
await interop.IncludeScripts(scripts.ToArray());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
_scriptsloaded = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,6 +128,14 @@ namespace Oqtane.Modules
|
|||||||
return PageState?.RenderId == ModuleState?.RenderId;
|
return PageState?.RenderId == ModuleState?.RenderId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool ScriptsLoaded
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _scriptsloaded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// path method
|
// path method
|
||||||
|
|
||||||
public string ModulePath()
|
public string ModulePath()
|
||||||
@ -132,6 +143,15 @@ namespace Oqtane.Modules
|
|||||||
return PageState?.Alias.BaseUrl + "/Modules/" + GetType().Namespace + "/";
|
return PageState?.Alias.BaseUrl + "/Modules/" + GetType().Namespace + "/";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fingerprint hash code for static assets
|
||||||
|
public string Fingerprint
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return ModuleState.ModuleDefinition.Fingerprint;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// url methods
|
// url methods
|
||||||
|
|
||||||
// navigate url
|
// navigate url
|
||||||
@ -394,6 +414,79 @@ namespace Oqtane.Modules
|
|||||||
await interop.ScrollTo(0, 0, "smooth");
|
await interop.ScrollTo(0, 0, "smooth");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string ReplaceTokens(string content)
|
||||||
|
{
|
||||||
|
return ReplaceTokens(content, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ReplaceTokens(string content, object obj)
|
||||||
|
{
|
||||||
|
var tokens = new List<string>();
|
||||||
|
var pos = content.IndexOf("[");
|
||||||
|
if (pos != -1)
|
||||||
|
{
|
||||||
|
if (content.IndexOf("]", pos) != -1)
|
||||||
|
{
|
||||||
|
var token = content.Substring(pos, content.IndexOf("]", pos) - pos + 1);
|
||||||
|
if (token.Contains(":"))
|
||||||
|
{
|
||||||
|
tokens.Add(token.Substring(1, token.Length - 2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pos = content.IndexOf("[", pos + 1);
|
||||||
|
}
|
||||||
|
if (tokens.Count != 0)
|
||||||
|
{
|
||||||
|
foreach (string token in tokens)
|
||||||
|
{
|
||||||
|
var segments = token.Split(":");
|
||||||
|
if (segments.Length >= 2 && segments.Length <= 3)
|
||||||
|
{
|
||||||
|
var objectName = string.Join(":", segments, 0, segments.Length - 1);
|
||||||
|
var propertyName = segments[segments.Length - 1];
|
||||||
|
var propertyValue = "";
|
||||||
|
|
||||||
|
switch (objectName)
|
||||||
|
{
|
||||||
|
case "ModuleState":
|
||||||
|
propertyValue = ModuleState.GetType().GetProperty(propertyName)?.GetValue(ModuleState, null).ToString();
|
||||||
|
break;
|
||||||
|
case "PageState":
|
||||||
|
propertyValue = PageState.GetType().GetProperty(propertyName)?.GetValue(PageState, null).ToString();
|
||||||
|
break;
|
||||||
|
case "PageState:Alias":
|
||||||
|
propertyValue = PageState.Alias.GetType().GetProperty(propertyName)?.GetValue(PageState.Alias, null).ToString();
|
||||||
|
break;
|
||||||
|
case "PageState:Site":
|
||||||
|
propertyValue = PageState.Site.GetType().GetProperty(propertyName)?.GetValue(PageState.Site, null).ToString();
|
||||||
|
break;
|
||||||
|
case "PageState:Page":
|
||||||
|
propertyValue = PageState.Page.GetType().GetProperty(propertyName)?.GetValue(PageState.Page, null).ToString();
|
||||||
|
break;
|
||||||
|
case "PageState:User":
|
||||||
|
propertyValue = PageState.User?.GetType().GetProperty(propertyName)?.GetValue(PageState.User, null).ToString();
|
||||||
|
break;
|
||||||
|
case "PageState:Route":
|
||||||
|
propertyValue = PageState.Route.GetType().GetProperty(propertyName)?.GetValue(PageState.Route, null).ToString();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (obj != null && obj.GetType().Name == objectName)
|
||||||
|
{
|
||||||
|
propertyValue = obj.GetType().GetProperty(propertyName)?.GetValue(obj, null).ToString();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (propertyValue != null)
|
||||||
|
{
|
||||||
|
content = content.Replace("[" + token + "]", propertyValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
// logging methods
|
// logging methods
|
||||||
public async Task Log(Alias alias, LogLevel level, string function, Exception exception, string message, params object[] args)
|
public async Task Log(Alias alias, LogLevel level, string function, Exception exception, string message, params object[] args)
|
||||||
{
|
{
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<TargetFramework>net9.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<Configurations>Debug;Release</Configurations>
|
<Configurations>Debug;Release</Configurations>
|
||||||
<Version>6.0.1</Version>
|
<Version>6.1.1</Version>
|
||||||
<Product>Oqtane</Product>
|
<Product>Oqtane</Product>
|
||||||
<Authors>Shaun Walker</Authors>
|
<Authors>Shaun Walker</Authors>
|
||||||
<Company>.NET Foundation</Company>
|
<Company>.NET Foundation</Company>
|
||||||
@ -12,7 +12,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/v6.0.1</PackageReleaseNotes>
|
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.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>
|
||||||
@ -22,10 +22,10 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.3" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="9.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="9.0.3" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.3" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -8,20 +8,20 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"profiles": {
|
"profiles": {
|
||||||
"IIS Express": {
|
"Oqtane": {
|
||||||
"commandName": "IISExpress",
|
|
||||||
"launchBrowser": true,
|
|
||||||
"environmentVariables": {
|
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Oqtane.Client": {
|
|
||||||
"commandName": "Project",
|
"commandName": "Project",
|
||||||
"launchBrowser": true,
|
"launchBrowser": true,
|
||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
},
|
},
|
||||||
"applicationUrl": "http://localhost:44358/"
|
"applicationUrl": "http://localhost:44358/"
|
||||||
|
},
|
||||||
|
"IIS Express": {
|
||||||
|
"commandName": "IISExpress",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -186,4 +186,10 @@
|
|||||||
<data name="Message.Username.Invalid" xml:space="preserve">
|
<data name="Message.Username.Invalid" xml:space="preserve">
|
||||||
<value>The Username Provided Does Not Meet The System Requirement, It Can Only Contains Letters Or Digits.</value>
|
<value>The Username Provided Does Not Meet The System Requirement, It Can Only Contains Letters Or Digits.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Name.Text" xml:space="preserve">
|
||||||
|
<value>Full Name:</value>
|
||||||
|
</data>
|
||||||
|
<data name="Name.HelpText" xml:space="preserve">
|
||||||
|
<value>Provide the full name of the host user</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
@ -175,7 +175,7 @@
|
|||||||
<value>Capacity:</value>
|
<value>Capacity:</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ImageSizes.HelpText" xml:space="preserve">
|
<data name="ImageSizes.HelpText" xml:space="preserve">
|
||||||
<value>Enter a list of image sizes which can be generated dynamically from uploaded images (ie. 200x200,400x400). Use * to indicate the folder supports all image sizes.</value>
|
<value>Optionally enter a list of image sizes which can be generated dynamically from uploaded images (ie. 200x200,400x400). Use * to indicate the folder supports all image sizes (not recommended).</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ImageSizes.Text" xml:space="preserve">
|
<data name="ImageSizes.Text" xml:space="preserve">
|
||||||
<value>Image Sizes:</value>
|
<value>Image Sizes:</value>
|
||||||
@ -198,4 +198,10 @@
|
|||||||
<data name="Settings.Heading" xml:space="preserve">
|
<data name="Settings.Heading" xml:space="preserve">
|
||||||
<value>Settings</value>
|
<value>Settings</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="CacheControl.Text" xml:space="preserve">
|
||||||
|
<value>Caching:</value>
|
||||||
|
</data>
|
||||||
|
<data name="CacheControl.HelpText" xml:space="preserve">
|
||||||
|
<value>Optionally provide a Cache-Control directive for this folder. For example 'public, max-age=60' indicates that files in this folder should be cached for 60 seconds. Please note that when caching is enabled, changes to files will not be immediately reflected in the UI.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
@ -165,4 +165,31 @@
|
|||||||
<data name="UploadFiles.Text" xml:space="preserve">
|
<data name="UploadFiles.Text" xml:space="preserve">
|
||||||
<value>Upload Files</value>
|
<value>Upload Files</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Files.Heading" xml:space="preserve">
|
||||||
|
<value>Files</value>
|
||||||
|
</data>
|
||||||
|
<data name="ImageExtensions.Text" xml:space="preserve">
|
||||||
|
<value>Image Extensions:</value>
|
||||||
|
</data>
|
||||||
|
<data name="ImageExtensions.HelpText" xml:space="preserve">
|
||||||
|
<value>Enter a comma separated list of image file extensions</value>
|
||||||
|
</data>
|
||||||
|
<data name="UploadableFileExtensions.Text" xml:space="preserve">
|
||||||
|
<value>Uploadable File Extensions:</value>
|
||||||
|
</data>
|
||||||
|
<data name="UploadableFileExtensions.HelpText" xml:space="preserve">
|
||||||
|
<value>Enter a comma separated list of uploadable file extensions</value>
|
||||||
|
</data>
|
||||||
|
<data name="MaxChunkSize.Text" xml:space="preserve">
|
||||||
|
<value>Max Upload Chunk Size (MB):</value>
|
||||||
|
</data>
|
||||||
|
<data name="MaxChunkSize.HelpText" xml:space="preserve">
|
||||||
|
<value>Files are split into chunks to streamline the upload process. Specify the maximum chunk size in MB (note that higher chunk sizes should only be used on faster networks).</value>
|
||||||
|
</data>
|
||||||
|
<data name="Success.SaveSiteSettings" xml:space="preserve">
|
||||||
|
<value>Settings Saved Successfully</value>
|
||||||
|
</data>
|
||||||
|
<data name="Error.SaveSiteSettings" xml:space="preserve">
|
||||||
|
<value>Error Saving Settings</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
@ -180,9 +180,6 @@
|
|||||||
<data name="Once" xml:space="preserve">
|
<data name="Once" xml:space="preserve">
|
||||||
<value>Execute Once</value>
|
<value>Execute Once</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Message.NoJobs" xml:space="preserve">
|
|
||||||
<value>Please Note That After An Initial Installation You Must <a href={0}>Restart</a> The Application In Order To Activate The Default Scheduled Jobs.</value>
|
|
||||||
</data>
|
|
||||||
<data name="Refresh.Text" xml:space="preserve">
|
<data name="Refresh.Text" xml:space="preserve">
|
||||||
<value>Refresh</value>
|
<value>Refresh</value>
|
||||||
</data>
|
</data>
|
||||||
|
@ -228,18 +228,6 @@
|
|||||||
<data name="View License" xml:space="preserve">
|
<data name="View License" xml:space="preserve">
|
||||||
<value>View License</value>
|
<value>View License</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Error.Validate" xml:space="preserve">
|
|
||||||
<value>Error Validating Package</value>
|
|
||||||
</data>
|
|
||||||
<data name="Message.Download" xml:space="preserve">
|
|
||||||
<value>Package Version Has Been Verified. Please Select The Download Button To Obtain The Package.</value>
|
|
||||||
</data>
|
|
||||||
<data name="Message.Validate" xml:space="preserve">
|
|
||||||
<value>This Package Version Has Not Been Registered In The Oqtane Marketplace Or You Do Not Have The Right To Use It From This Installation</value>
|
|
||||||
</data>
|
|
||||||
<data name="Validate" xml:space="preserve">
|
|
||||||
<value>Validate</value>
|
|
||||||
</data>
|
|
||||||
<data name="Browse" xml:space="preserve">
|
<data name="Browse" xml:space="preserve">
|
||||||
<value>Browse</value>
|
<value>Browse</value>
|
||||||
</data>
|
</data>
|
||||||
|
@ -169,7 +169,7 @@
|
|||||||
<value>Select whether the page is part of the site navigation or hidden</value>
|
<value>Select whether the page is part of the site navigation or hidden</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="UrlPath.HelpText" xml:space="preserve">
|
<data name="UrlPath.HelpText" xml:space="preserve">
|
||||||
<value>Optionally enter a url path for this page (ie. home ). If you do not provide a url path, the page name will be used. If the page is intended to be the root path specify '/'.</value>
|
<value>Optionally enter a url path for this page (ie. home ). If you do not provide a url path, the page name will be used. Please note that spaces and punctuation will be replaced by a dash. If the page is intended to be the root path specify '/'.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Redirect.HelpText" xml:space="preserve">
|
<data name="Redirect.HelpText" xml:space="preserve">
|
||||||
<value>Optionally enter a url which this page should redirect to when a user navigates to it</value>
|
<value>Optionally enter a url which this page should redirect to when a user navigates to it</value>
|
||||||
@ -297,4 +297,10 @@
|
|||||||
<data name="ExpiryDate.Text" xml:space="preserve">
|
<data name="ExpiryDate.Text" xml:space="preserve">
|
||||||
<value>Expiry Date: </value>
|
<value>Expiry Date: </value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="PersonalizedUrlPath.Text" xml:space="preserve">
|
||||||
|
<value>Url Path:</value>
|
||||||
|
</data>
|
||||||
|
<data name="PersonalizedUrlPath.HelpText" xml:space="preserve">
|
||||||
|
<value>Provide a url path for your personalized page. Please note that spaces and punctuation will be replaced by a dash.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
@ -195,4 +195,25 @@
|
|||||||
<data name="Modules.Heading" xml:space="preserve">
|
<data name="Modules.Heading" xml:space="preserve">
|
||||||
<value>Modules</value>
|
<value>Modules</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Message.Page.Restore" xml:space="preserve">
|
||||||
|
<value>You Cannot Restore A Page If Its Parent Is Deleted</value>
|
||||||
|
</data>
|
||||||
|
<data name="Success.Page.Restore" xml:space="preserve">
|
||||||
|
<value>Page Restored Successfully</value>
|
||||||
|
</data>
|
||||||
|
<data name="Success.Page.Delete" xml:space="preserve">
|
||||||
|
<value>Page Deleted Successfully</value>
|
||||||
|
</data>
|
||||||
|
<data name="Success.Pages.Deleted" xml:space="preserve">
|
||||||
|
<value>All Pages Deleted Successfully</value>
|
||||||
|
</data>
|
||||||
|
<data name="Success.Module.Restore" xml:space="preserve">
|
||||||
|
<value>Module Restored Successfully</value>
|
||||||
|
</data>
|
||||||
|
<data name="Success.Module.Delete" xml:space="preserve">
|
||||||
|
<value>Module Deleted Successfully</value>
|
||||||
|
</data>
|
||||||
|
<data name="Success.Modules.Delete" xml:space="preserve">
|
||||||
|
<value>All Modules Deleted Successfully</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
@ -118,7 +118,7 @@
|
|||||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
</resheader>
|
</resheader>
|
||||||
<data name="NoCriteria" xml:space="preserve">
|
<data name="NoCriteria" xml:space="preserve">
|
||||||
<value>You Must Provide Some Search Criteria</value>
|
<value>Please Enter Some Search Criteria</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="NoResult" xml:space="preserve">
|
<data name="NoResult" xml:space="preserve">
|
||||||
<value>No Content Matches The Criteria Provided</value>
|
<value>No Content Matches The Criteria Provided</value>
|
||||||
|
@ -349,7 +349,7 @@
|
|||||||
<value>Relay Configured?</value>
|
<value>Relay Configured?</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SiteMap.HelpText" xml:space="preserve">
|
<data name="SiteMap.HelpText" xml:space="preserve">
|
||||||
<value>The site map url for this site which can be submitted to search engines for indexing</value>
|
<value>The site map url for this site which can be submitted to search engines for indexing. The sitemap is cached for 5 minutes and the cache can be manually cleared.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SiteMap.Text" xml:space="preserve">
|
<data name="SiteMap.Text" xml:space="preserve">
|
||||||
<value>Site Map:</value>
|
<value>Site Map:</value>
|
||||||
@ -402,18 +402,6 @@
|
|||||||
<data name="Retention.Text" xml:space="preserve">
|
<data name="Retention.Text" xml:space="preserve">
|
||||||
<value>Retention (Days):</value>
|
<value>Retention (Days):</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ImageExtensions.HelpText" xml:space="preserve">
|
|
||||||
<value>Enter a comma separated list of image file extensions</value>
|
|
||||||
</data>
|
|
||||||
<data name="ImageExtensions.Text" xml:space="preserve">
|
|
||||||
<value>Image Extensions:</value>
|
|
||||||
</data>
|
|
||||||
<data name="UploadableFileExtensions.HelpText" xml:space="preserve">
|
|
||||||
<value>Enter a comma separated list of uploadable file extensions</value>
|
|
||||||
</data>
|
|
||||||
<data name="UploadableFileExtensions.Text" xml:space="preserve">
|
|
||||||
<value>Uploadable File Extensions:</value>
|
|
||||||
</data>
|
|
||||||
<data name="HybridEnabled.HelpText" xml:space="preserve">
|
<data name="HybridEnabled.HelpText" xml:space="preserve">
|
||||||
<value>Specifies if the site can be integrated with an external .NET MAUI hybrid application</value>
|
<value>Specifies if the site can be integrated with an external .NET MAUI hybrid application</value>
|
||||||
</data>
|
</data>
|
||||||
@ -438,4 +426,25 @@
|
|||||||
<data name="System" xml:space="preserve">
|
<data name="System" xml:space="preserve">
|
||||||
<value>System</value>
|
<value>System</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="CookieConsent.HelpText" xml:space="preserve">
|
||||||
|
<value>Specify if cookie consent is enabled on this site. Please note this option must be used in conjunction with a Theme which supports cookie consent.</value>
|
||||||
|
</data>
|
||||||
|
<data name="CookieConsent.Text" xml:space="preserve">
|
||||||
|
<value>Cookie Consent:</value>
|
||||||
|
</data>
|
||||||
|
<data name="OptIn" xml:space="preserve">
|
||||||
|
<value>Opt-In (GDPR)</value>
|
||||||
|
</data>
|
||||||
|
<data name="OptOut" xml:space="preserve">
|
||||||
|
<value>Opt-Out (CCPA)</value>
|
||||||
|
</data>
|
||||||
|
<data name="Theme.Heading" xml:space="preserve">
|
||||||
|
<value>Theme</value>
|
||||||
|
</data>
|
||||||
|
<data name="SiteMap.EvictCache" xml:space="preserve">
|
||||||
|
<value>Clear Cache</value>
|
||||||
|
</data>
|
||||||
|
<data name="Success.SiteMap.CacheEvicted" xml:space="preserve">
|
||||||
|
<value>Site Map Cache Cleared</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
@ -117,8 +117,8 @@
|
|||||||
<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="Access.ApiFramework" xml:space="preserve">
|
<data name="Swagger" xml:space="preserve">
|
||||||
<value>Access Swagger API</value>
|
<value>Swagger UI</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FrameworkVersion.HelpText" xml:space="preserve">
|
<data name="FrameworkVersion.HelpText" xml:space="preserve">
|
||||||
<value>Framework Version</value>
|
<value>Framework Version</value>
|
||||||
@ -220,10 +220,10 @@
|
|||||||
<value>You Have Been Successfully Registered For Updates</value>
|
<value>You Have Been Successfully Registered For Updates</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PackageManager.HelpText" xml:space="preserve">
|
<data name="PackageManager.HelpText" xml:space="preserve">
|
||||||
<value>Specify The Package Manager Service For Installing Modules, Themes, And Translations. If This Field Is Blank It Means The Package Manager Service Is Disabled For This Installation.</value>
|
<value>Specify The Url Of The Package Manager Service For Installing Modules, Themes, And Translations. If This Field Is Blank It Means The Package Manager Service Is Disabled For This Installation.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="PackageManager.Text" xml:space="preserve">
|
<data name="PackageManager.Text" xml:space="preserve">
|
||||||
<value>Package Manager:</value>
|
<value>Package Manager Url:</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Swagger.HelpText" xml:space="preserve">
|
<data name="Swagger.HelpText" xml:space="preserve">
|
||||||
<value>Specify If Swagger Is Enabled For Your Server API</value>
|
<value>Specify If Swagger Is Enabled For Your Server API</value>
|
||||||
@ -294,4 +294,19 @@
|
|||||||
<data name="Process.Text" xml:space="preserve">
|
<data name="Process.Text" xml:space="preserve">
|
||||||
<value>Process: </value>
|
<value>Process: </value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="PackageManagerEmail.Text" xml:space="preserve">
|
||||||
|
<value>Package Manager Email:</value>
|
||||||
|
</data>
|
||||||
|
<data name="PackageManagerEmail.HelpText" xml:space="preserve">
|
||||||
|
<value>Specify The Email Address Of The User Account Used For Interacting With The Package Manager Service. This Account Is Used For Managing Packages Across Multiple Installations.</value>
|
||||||
|
</data>
|
||||||
|
<data name="CacheControl.Text" xml:space="preserve">
|
||||||
|
<value>Static Asset Caching:</value>
|
||||||
|
</data>
|
||||||
|
<data name="CacheControl.HelpText" xml:space="preserve">
|
||||||
|
<value>Provide a Cache-Control directive for static assets. For example 'public, max-age=60' indicates that static assets should be cached for 60 seconds. A blank value indicates caching is not enabled.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Endpoints" xml:space="preserve">
|
||||||
|
<value>API Endpoints</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
@ -180,16 +180,4 @@
|
|||||||
<data name="View License" xml:space="preserve">
|
<data name="View License" xml:space="preserve">
|
||||||
<value>View License</value>
|
<value>View License</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Error.Validate" xml:space="preserve">
|
|
||||||
<value>Error Validating Package</value>
|
|
||||||
</data>
|
|
||||||
<data name="Message.Download" xml:space="preserve">
|
|
||||||
<value>Package Version Has Been Verified. Please Select The Download Button To Obtain The Package.</value>
|
|
||||||
</data>
|
|
||||||
<data name="Message.Validate" xml:space="preserve">
|
|
||||||
<value>This Package Version Has Not Been Registered In The Oqtane Marketplace Or You Do Not Have The Right To Use It From This Installation</value>
|
|
||||||
</data>
|
|
||||||
<data name="Validate" xml:space="preserve">
|
|
||||||
<value>Validate</value>
|
|
||||||
</data>
|
|
||||||
</root>
|
</root>
|
@ -156,4 +156,7 @@
|
|||||||
<data name="Enabled" xml:space="preserve">
|
<data name="Enabled" xml:space="preserve">
|
||||||
<value>Enabled?</value>
|
<value>Enabled?</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Assign" xml:space="preserve">
|
||||||
|
<value>Assign</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
@ -162,4 +162,10 @@
|
|||||||
<data name="Edit.Text" xml:space="preserve">
|
<data name="Edit.Text" xml:space="preserve">
|
||||||
<value>Edit</value>
|
<value>Edit</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Retention.Text" xml:space="preserve">
|
||||||
|
<value>Retention (Days):</value>
|
||||||
|
</data>
|
||||||
|
<data name="Retention.HelpText" xml:space="preserve">
|
||||||
|
<value>Number of days of broken urls to retain</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
@ -195,4 +195,19 @@
|
|||||||
<data name="LastLogin.Text" xml:space="preserve">
|
<data name="LastLogin.Text" xml:space="preserve">
|
||||||
<value>Last Login:</value>
|
<value>Last Login:</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="DeleteUser.Header" xml:space="preserve">
|
||||||
|
<value>Delete User</value>
|
||||||
|
</data>
|
||||||
|
<data name="DeleteUser.Text" xml:space="preserve">
|
||||||
|
<value>Delete</value>
|
||||||
|
</data>
|
||||||
|
<data name="DeleteUser.Message" xml:space="preserve">
|
||||||
|
<value>Are You Sure You Wish To Permanently Delete This User?</value>
|
||||||
|
</data>
|
||||||
|
<data name="Impersonate" xml:space="preserve">
|
||||||
|
<value>Impersonate</value>
|
||||||
|
</data>
|
||||||
|
<data name="Error.User.Impersonate" xml:space="preserve">
|
||||||
|
<value>Unable To Impersonate User</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
@ -495,4 +495,22 @@
|
|||||||
<data name="OIDC" xml:space="preserve">
|
<data name="OIDC" xml:space="preserve">
|
||||||
<value>OpenID Connect (OIDC)</value>
|
<value>OpenID Connect (OIDC)</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SaveTokens.Text" xml:space="preserve">
|
||||||
|
<value>Save Tokens?</value>
|
||||||
|
</data>
|
||||||
|
<data name="SaveTokens.HelpText" xml:space="preserve">
|
||||||
|
<value>Specify whether access and refresh tokens should be saved after a successful login. The default is false to reduce the size of the authentication cookie.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Success.DeleteUser" xml:space="preserve">
|
||||||
|
<value>User Deleted Successfully</value>
|
||||||
|
</data>
|
||||||
|
<data name="Error.DeleteUser" xml:space="preserve">
|
||||||
|
<value>Error Deleting User</value>
|
||||||
|
</data>
|
||||||
|
<data name="LogoutEverywhere.Text" xml:space="preserve">
|
||||||
|
<value>Logout Everywhere?</value>
|
||||||
|
</data>
|
||||||
|
<data name="LogoutEverywhere.HelpText" xml:space="preserve">
|
||||||
|
<value>Do you want users to be logged out of every active session on any device, or only their current session?</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
@ -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 Or Is Still In Progress</value>
|
<value>File Upload Failed</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>
|
||||||
|
126
Oqtane.Client/Resources/Modules/HtmlText/Settings.resx
Normal file
126
Oqtane.Client/Resources/Modules/HtmlText/Settings.resx
Normal 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="DynamicTokens.HelpText" xml:space="preserve">
|
||||||
|
<value>Do you wish to allow tokens to be dynamically replaced? Please note that this will affect the performance of your site.</value>
|
||||||
|
</data>
|
||||||
|
<data name="DynamicTokens.Text" xml:space="preserve">
|
||||||
|
<value>Dynamic Tokens?</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
@ -427,7 +427,7 @@
|
|||||||
<value>At Least One Uppercase Letter</value>
|
<value>At Least One Uppercase Letter</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Password.ValidationCriteria" xml:space="preserve">
|
<data name="Password.ValidationCriteria" xml:space="preserve">
|
||||||
<value>Passwords Must Have A Minimum Length Of {0} Characters, Including At Least {1} Unique Character(s), {2}{3}{4}{5} To Satisfy Password Compexity Requirements For This Site.</value>
|
<value>Passwords Must Have A Minimum Length Of {0} Characters, Including At Least {1} Unique Character(s), {2}{3}{4}{5} To Satisfy Password Complexity Requirements For This Site.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="ProfileInvalid" xml:space="preserve">
|
<data name="ProfileInvalid" xml:space="preserve">
|
||||||
<value>{0} Is Not Valid</value>
|
<value>{0} Is Not Valid</value>
|
||||||
@ -474,4 +474,7 @@
|
|||||||
<data name="User" xml:space="preserve">
|
<data name="User" xml:space="preserve">
|
||||||
<value>User</value>
|
<value>User</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Path" xml:space="preserve">
|
||||||
|
<value>Path</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
129
Oqtane.Client/Resources/Themes/Controls/CookieConsent.resx
Normal file
129
Oqtane.Client/Resources/Themes/Controls/CookieConsent.resx
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
<?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="Confirm" xml:space="preserve">
|
||||||
|
<value>Confirm</value>
|
||||||
|
</data>
|
||||||
|
<data name="ConsentNotice" xml:space="preserve">
|
||||||
|
<value>I agree to use cookies to provide the best possible user experience for this site. I understand that I can change these preferences at any time.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Privacy" xml:space="preserve">
|
||||||
|
<value>Privacy</value>
|
||||||
|
</data>
|
||||||
|
</root>
|
@ -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
|
||||||
|
47
Oqtane.Client/Services/CookieConsentService.cs
Normal file
47
Oqtane.Client/Services/CookieConsentService.cs
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
using Oqtane.Models;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System;
|
||||||
|
using Oqtane.Documentation;
|
||||||
|
using Oqtane.Shared;
|
||||||
|
using System.Globalization;
|
||||||
|
|
||||||
|
namespace Oqtane.Services
|
||||||
|
{
|
||||||
|
/// <inheritdoc cref="ICookieConsentService" />
|
||||||
|
[PrivateApi("Don't show in the documentation, as everything should use the Interface")]
|
||||||
|
public class CookieConsentService : ServiceBase, ICookieConsentService
|
||||||
|
{
|
||||||
|
public CookieConsentService(HttpClient http, SiteState siteState) : base(http, siteState) { }
|
||||||
|
|
||||||
|
private string ApiUrl => CreateApiUrl("CookieConsent");
|
||||||
|
|
||||||
|
public async Task<bool> IsActionedAsync()
|
||||||
|
{
|
||||||
|
return await GetJsonAsync<bool>($"{ApiUrl}/IsActioned");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> CanTrackAsync(bool optOut)
|
||||||
|
{
|
||||||
|
return await GetJsonAsync<bool>($"{ApiUrl}/CanTrack?optout=" + optOut);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> CreateActionedCookieAsync()
|
||||||
|
{
|
||||||
|
var cookie = await GetStringAsync($"{ApiUrl}/CreateActionedCookie");
|
||||||
|
return cookie ?? string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> CreateConsentCookieAsync()
|
||||||
|
{
|
||||||
|
var cookie = await GetStringAsync($"{ApiUrl}/CreateConsentCookie");
|
||||||
|
return cookie ?? string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> WithdrawConsentCookieAsync()
|
||||||
|
{
|
||||||
|
var cookie = await GetStringAsync($"{ApiUrl}/WithdrawConsentCookie");
|
||||||
|
return cookie ?? string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -56,10 +56,5 @@ namespace Oqtane.Services
|
|||||||
{
|
{
|
||||||
await PostAsync($"{ApiUrl}/restart");
|
await PostAsync($"{ApiUrl}/restart");
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RegisterAsync(string email)
|
|
||||||
{
|
|
||||||
await PostJsonAsync($"{ApiUrl}/register?email={WebUtility.UrlEncode(email)}", true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
42
Oqtane.Client/Services/Interfaces/ICookieConsentService.cs
Normal file
42
Oqtane.Client/Services/Interfaces/ICookieConsentService.cs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
using Oqtane.Models;
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Oqtane.Services
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Service to retrieve cookie consent information.
|
||||||
|
/// </summary>
|
||||||
|
public interface ICookieConsentService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Get cookie consent bar actioned status
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
Task<bool> IsActionedAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get cookie consent status
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
Task<bool> CanTrackAsync(bool optOut);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// create actioned cookie
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
Task<string> CreateActionedCookieAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// create consent cookie
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
Task<string> CreateConsentCookieAsync();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// widhdraw consent cookie
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
Task<string> WithdrawConsentCookieAsync();
|
||||||
|
}
|
||||||
|
}
|
@ -34,13 +34,5 @@ namespace Oqtane.Services
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>internal status/message object</returns>
|
/// <returns>internal status/message object</returns>
|
||||||
Task RestartAsync();
|
Task RestartAsync();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Registers a new <see cref="User"/>
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="email">Email of the user to be registered</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
Task RegisterAsync(string email);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
18
Oqtane.Client/Services/Interfaces/IOutputCacheService.cs
Normal file
18
Oqtane.Client/Services/Interfaces/IOutputCacheService.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Oqtane.Services
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Service to manage cache
|
||||||
|
/// </summary>
|
||||||
|
public interface IOutputCacheService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Evicts the output cache for a specific tag
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tag"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
Task EvictByTag(string tag);
|
||||||
|
}
|
||||||
|
}
|
23
Oqtane.Client/Services/OutputCacheService.cs
Normal file
23
Oqtane.Client/Services/OutputCacheService.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
using Oqtane.Documentation;
|
||||||
|
using Oqtane.Shared;
|
||||||
|
|
||||||
|
namespace Oqtane.Services
|
||||||
|
{
|
||||||
|
/// <inheritdoc cref="IOutputCacheService" />
|
||||||
|
[PrivateApi("Don't show in the documentation, as everything should use the Interface")]
|
||||||
|
public class OutputCacheService : ServiceBase, IOutputCacheService
|
||||||
|
{
|
||||||
|
public OutputCacheService(HttpClient http, SiteState siteState) : base(http, siteState) { }
|
||||||
|
|
||||||
|
private string ApiUrl => CreateApiUrl("OutputCache");
|
||||||
|
|
||||||
|
public async Task EvictByTag(string tag)
|
||||||
|
{
|
||||||
|
await DeleteAsync($"{ApiUrl}/{tag}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,11 +1,6 @@
|
|||||||
@namespace Oqtane.Themes.BlazorTheme
|
@namespace Oqtane.Themes.BlazorTheme
|
||||||
@inherits ThemeBase
|
@inherits ThemeBase
|
||||||
|
|
||||||
|
|
||||||
<div class="breadcrumbs">
|
|
||||||
<Breadcrumbs />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row flex-xl-nowrap gx-0">
|
<div class="row flex-xl-nowrap gx-0">
|
||||||
<div class="sidebar">
|
<div class="sidebar">
|
||||||
<nav class="navbar">
|
<nav class="navbar">
|
||||||
@ -22,13 +17,18 @@
|
|||||||
<Login />
|
<Login />
|
||||||
<ControlPanel LanguageDropdownAlignment="right" />
|
<ControlPanel LanguageDropdownAlignment="right" />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="breadcrumbs">
|
||||||
|
<Breadcrumbs />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="row px-4">
|
<div class="row px-4">
|
||||||
<Pane Name="@PaneNames.Admin" />
|
<Pane Name="@PaneNames.Admin" />
|
||||||
|
<CookieConsent />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
|
@ -131,6 +131,7 @@
|
|||||||
if (PageState.Page.IsPersonalizable && PageState.User != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Registered))
|
if (PageState.Page.IsPersonalizable && PageState.User != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Registered))
|
||||||
{
|
{
|
||||||
page = await PageService.AddPageAsync(PageState.Page.PageId, PageState.User.UserId);
|
page = await PageService.AddPageAsync(PageState.Page.PageId, PageState.User.UserId);
|
||||||
|
PageState.EditMode = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_showEditMode)
|
if (_showEditMode)
|
||||||
@ -153,7 +154,7 @@
|
|||||||
{
|
{
|
||||||
if (PageState.Page.IsPersonalizable && PageState.User != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Registered))
|
if (PageState.Page.IsPersonalizable && PageState.User != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Registered))
|
||||||
{
|
{
|
||||||
NavigationManager.NavigateTo(NavigateUrl(page.Path, "edit=" + PageState.EditMode.ToString()));
|
NavigationManager.NavigateTo(NavigateUrl(page.Path, "edit=" + PageState.EditMode.ToString().ToLower()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -573,7 +573,7 @@
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// post to the Logout page to complete the logout process
|
// post to the Logout page to complete the logout process
|
||||||
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, returnurl = url };
|
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, returnurl = url, everywhere = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:LogoutEverywhere", "false")) };
|
||||||
var interop = new Interop(jsRuntime);
|
var interop = new Interop(jsRuntime);
|
||||||
await interop.SubmitForm(Utilities.TenantUrl(PageState.Alias, "/pages/logout/"), fields);
|
await interop.SubmitForm(Utilities.TenantUrl(PageState.Alias, "/pages/logout/"), fields);
|
||||||
}
|
}
|
||||||
|
168
Oqtane.Client/Themes/Controls/Theme/CookieConsent.razor
Normal file
168
Oqtane.Client/Themes/Controls/Theme/CookieConsent.razor
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
@namespace Oqtane.Themes.Controls
|
||||||
|
@inherits ThemeControlBase
|
||||||
|
@inject ISettingService SettingService
|
||||||
|
@inject ICookieConsentService CookieConsentService
|
||||||
|
@inject IStringLocalizer<CookieConsent> Localizer
|
||||||
|
|
||||||
|
@if (_enabled && !Hidden)
|
||||||
|
{
|
||||||
|
|
||||||
|
<div class="gdpr-consent-bar bg-light text-dark @(_showBanner ? "p-3" : "p-0") pe-5 fixed-bottom">
|
||||||
|
<form method="post" @formname="CookieConsentForm" @onsubmit="async () => await AcceptPolicy()" data-enhance>
|
||||||
|
@if (_showBanner)
|
||||||
|
{
|
||||||
|
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-9 col-xl-10">
|
||||||
|
@if (PageState.RenderMode == RenderModes.Static)
|
||||||
|
{
|
||||||
|
<input type="checkbox" name="cantrack" checked="@_canTrack" value="1" class="form-check-input me-2" />
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<input type="checkbox" name="cantrack" @bind="@_canTrack" value="1" class="form-check-input me-2" />
|
||||||
|
}
|
||||||
|
@((MarkupString)Convert.ToString(Localizer["ConsentNotice"]))
|
||||||
|
</div>
|
||||||
|
<div class="col-3 col-xl-2">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6 col-xs-6 text-center">
|
||||||
|
<button class="btn btn-primary mb-1 px-0 w-100" type="submit">@((MarkupString)Convert.ToString(Localizer["Confirm"]))</button>
|
||||||
|
</div>
|
||||||
|
@if (ShowPrivacyLink)
|
||||||
|
{
|
||||||
|
<div class="col-md-6 col-xs-6 text-center">
|
||||||
|
<a class="btn btn-secondary mb-1 px-0 w-100" href="/privacy" target="_blank">@((MarkupString)Convert.ToString(Localizer["Privacy"]))</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</form>
|
||||||
|
<form method="post" @formname="CookieConsentToggleForm" @onsubmit="async () => await ToggleBanner()" data-enhance>
|
||||||
|
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||||
|
@if (_showBanner)
|
||||||
|
{
|
||||||
|
<input type="hidden" name="showbanner" value="false" />
|
||||||
|
<button type="submit" class="btn btn-light text-dark btn-sm position-absolute btn-hide">
|
||||||
|
<i class="oi oi-chevron-bottom"></i>
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<input type="hidden" name="showbanner" value="true" />
|
||||||
|
<button type="submit" class="btn btn-light text-dark btn-sm position-absolute btn-show">
|
||||||
|
<i class="oi oi-chevron-top"></i>
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
@code {
|
||||||
|
private bool _showBanner;
|
||||||
|
private bool _enabled;
|
||||||
|
private bool _optout;
|
||||||
|
private bool _actioned;
|
||||||
|
private bool _canTrack;
|
||||||
|
private bool _consentPostback;
|
||||||
|
private bool _togglePostback;
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public bool Hidden { get; set; }
|
||||||
|
|
||||||
|
[Parameter]
|
||||||
|
public bool ShowPrivacyLink { get; set; } = true;
|
||||||
|
|
||||||
|
[SupplyParameterFromForm(FormName = "CookieConsentToggleForm")]
|
||||||
|
public string ShowBanner
|
||||||
|
{
|
||||||
|
get => "";
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_showBanner = bool.Parse(value);
|
||||||
|
_togglePostback = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[SupplyParameterFromForm(FormName = "CookieConsentForm")]
|
||||||
|
public string CanTrack
|
||||||
|
{
|
||||||
|
get => "";
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_canTrack = !string.IsNullOrEmpty(value);
|
||||||
|
_consentPostback = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
var cookieConsentSetting = SettingService.GetSetting(PageState.Site.Settings, "CookieConsent", string.Empty);
|
||||||
|
_enabled = !string.IsNullOrEmpty(cookieConsentSetting);
|
||||||
|
_optout = cookieConsentSetting == "optout";
|
||||||
|
_actioned = await CookieConsentService.IsActionedAsync();
|
||||||
|
|
||||||
|
if (!_consentPostback)
|
||||||
|
{
|
||||||
|
_canTrack = await CookieConsentService.CanTrackAsync(_optout);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_togglePostback)
|
||||||
|
{
|
||||||
|
_showBanner = !_actioned;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task AcceptPolicy()
|
||||||
|
{
|
||||||
|
var cookieString = string.Empty;
|
||||||
|
if (_optout)
|
||||||
|
{
|
||||||
|
cookieString = _canTrack ? await CookieConsentService.WithdrawConsentCookieAsync() : await CookieConsentService.CreateConsentCookieAsync();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cookieString = _canTrack ? await CookieConsentService.CreateConsentCookieAsync() : await CookieConsentService.WithdrawConsentCookieAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
//update the page state
|
||||||
|
PageState.AllowCookies = _canTrack;
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(cookieString))
|
||||||
|
{
|
||||||
|
var interop = new Interop(JSRuntime);
|
||||||
|
await interop.SetCookieString(cookieString);
|
||||||
|
|
||||||
|
_actioned = true;
|
||||||
|
_showBanner = false;
|
||||||
|
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ToggleBanner()
|
||||||
|
{
|
||||||
|
if (!_actioned)
|
||||||
|
{
|
||||||
|
var cookieString = await CookieConsentService.CreateActionedCookieAsync();
|
||||||
|
if (!string.IsNullOrEmpty(cookieString))
|
||||||
|
{
|
||||||
|
var interop = new Interop(JSRuntime);
|
||||||
|
await interop.SetCookieString(cookieString);
|
||||||
|
|
||||||
|
_actioned = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PageState.RenderMode == RenderModes.Interactive)
|
||||||
|
{
|
||||||
|
_showBanner = !_showBanner;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -8,14 +8,15 @@
|
|||||||
{
|
{
|
||||||
@if (PageState.Runtime == Runtime.Hybrid)
|
@if (PageState.Runtime == Runtime.Hybrid)
|
||||||
{
|
{
|
||||||
<button type="button" class="btn btn-primary" @onclick="LogoutUser">@Localizer["Logout"]</button>
|
<button type="button" class="@CssClass" @onclick="LogoutUser">@Localizer["Logout"]</button>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
<form method="post" class="app-form-inline" action="@logouturl" @formname="LogoutForm">
|
<form method="post" class="app-form-inline" action="@logouturl" @formname="LogoutForm">
|
||||||
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||||
<input type="hidden" name="returnurl" value="@returnurl" />
|
<input type="hidden" name="returnurl" value="@returnurl" />
|
||||||
<button type="submit" class="btn btn-primary">@Localizer["Logout"]</button>
|
<input type="hidden" name="everywhere" value="@everywhere" />
|
||||||
|
<button type="submit" class="@CssClass">@Localizer["Logout"]</button>
|
||||||
</form>
|
</form>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -23,7 +24,7 @@
|
|||||||
{
|
{
|
||||||
@if (ShowLogin)
|
@if (ShowLogin)
|
||||||
{
|
{
|
||||||
<a href="@loginurl" class="btn btn-primary">@SharedLocalizer["Login"]</a>
|
<a href="@loginurl" class="@CssClass">@SharedLocalizer["Login"]</a>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</span>
|
</span>
|
||||||
@ -32,4 +33,6 @@
|
|||||||
{
|
{
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public bool ShowLogin { get; set; } = true;
|
public bool ShowLogin { get; set; } = true;
|
||||||
|
[Parameter]
|
||||||
|
public string CssClass { get; set; } = "btn btn-primary";
|
||||||
}
|
}
|
@ -4,7 +4,6 @@ using System.Threading.Tasks;
|
|||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
using Microsoft.JSInterop;
|
using Microsoft.JSInterop;
|
||||||
using Oqtane.Enums;
|
using Oqtane.Enums;
|
||||||
using Oqtane.Models;
|
|
||||||
using Oqtane.Providers;
|
using Oqtane.Providers;
|
||||||
using Oqtane.Security;
|
using Oqtane.Security;
|
||||||
using Oqtane.Services;
|
using Oqtane.Services;
|
||||||
@ -26,6 +25,7 @@ namespace Oqtane.Themes.Controls
|
|||||||
protected string loginurl;
|
protected string loginurl;
|
||||||
protected string logouturl;
|
protected string logouturl;
|
||||||
protected string returnurl;
|
protected string returnurl;
|
||||||
|
protected string everywhere;
|
||||||
|
|
||||||
protected override void OnParametersSet()
|
protected override void OnParametersSet()
|
||||||
{
|
{
|
||||||
@ -57,6 +57,7 @@ namespace Oqtane.Themes.Controls
|
|||||||
|
|
||||||
// set logout url
|
// set logout url
|
||||||
logouturl = Utilities.TenantUrl(PageState.Alias, "/pages/logout/");
|
logouturl = Utilities.TenantUrl(PageState.Alias, "/pages/logout/");
|
||||||
|
everywhere = SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:LogoutEverywhere", "false");
|
||||||
|
|
||||||
// verify anonymous users can access current page
|
// verify anonymous users can access current page
|
||||||
if (UserSecurity.IsAuthorized(null, PermissionNames.View, PageState.Page.PermissionList) && Utilities.IsEffectiveAndNotExpired(PageState.Page.EffectiveDate, PageState.Page.ExpiryDate))
|
if (UserSecurity.IsAuthorized(null, PermissionNames.View, PageState.Page.PermissionList) && Utilities.IsEffectiveAndNotExpired(PageState.Page.EffectiveDate, PageState.Page.ExpiryDate))
|
||||||
@ -98,7 +99,7 @@ namespace Oqtane.Themes.Controls
|
|||||||
else // this condition is only valid for legacy Login button inheriting from LoginBase
|
else // this condition is only valid for legacy Login button inheriting from LoginBase
|
||||||
{
|
{
|
||||||
// post to the Logout page to complete the logout process
|
// post to the Logout page to complete the logout process
|
||||||
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, returnurl = returnurl };
|
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, returnurl = returnurl, everywhere = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:LogoutEverywhere", "false")) };
|
||||||
var interop = new Interop(jsRuntime);
|
var interop = new Interop(jsRuntime);
|
||||||
await interop.SubmitForm(logouturl, fields);
|
await interop.SubmitForm(logouturl, fields);
|
||||||
}
|
}
|
||||||
|
@ -8,13 +8,13 @@
|
|||||||
<span class="app-profile">
|
<span class="app-profile">
|
||||||
@if (PageState.User != null)
|
@if (PageState.User != null)
|
||||||
{
|
{
|
||||||
<a href="@NavigateUrl("profile", "returnurl=" + _returnurl)" class="btn btn-primary">@PageState.User.Username</a>
|
<a href="@NavigateUrl("profile", "returnurl=" + _returnurl)" class="@CssClass">@PageState.User.Username</a>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@if (ShowRegister && PageState.Site.AllowRegistration)
|
@if (ShowRegister && PageState.Site.AllowRegistration)
|
||||||
{
|
{
|
||||||
<a href="@NavigateUrl("register", "returnurl=" + _returnurl)" class="btn btn-primary">@Localizer["Register"]</a>
|
<a href="@NavigateUrl("register", "returnurl=" + _returnurl)" class="@CssClass">@Localizer["Register"]</a>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</span>
|
</span>
|
||||||
@ -23,6 +23,8 @@
|
|||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public bool ShowRegister { get; set; }
|
public bool ShowRegister { get; set; }
|
||||||
|
[Parameter]
|
||||||
|
public string CssClass { get; set; } = "btn btn-primary";
|
||||||
|
|
||||||
private string _returnurl = "";
|
private string _returnurl = "";
|
||||||
|
|
||||||
|
@ -107,6 +107,7 @@
|
|||||||
{
|
{
|
||||||
<Pane Name="Footer" />
|
<Pane Name="Footer" />
|
||||||
}
|
}
|
||||||
|
<CookieConsent />
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
@ -15,6 +15,8 @@ namespace Oqtane.Themes
|
|||||||
{
|
{
|
||||||
public abstract class ThemeBase : ComponentBase, IThemeControl
|
public abstract class ThemeBase : ComponentBase, IThemeControl
|
||||||
{
|
{
|
||||||
|
private bool _scriptsloaded = false;
|
||||||
|
|
||||||
[Inject]
|
[Inject]
|
||||||
protected ILogService LoggingService { get; set; }
|
protected ILogService LoggingService { get; set; }
|
||||||
|
|
||||||
@ -62,7 +64,7 @@ namespace Oqtane.Themes
|
|||||||
var inline = 0;
|
var inline = 0;
|
||||||
foreach (Resource resource in resources)
|
foreach (Resource resource in resources)
|
||||||
{
|
{
|
||||||
if ((string.IsNullOrEmpty(resource.RenderMode) || resource.RenderMode == RenderModes.Interactive) && !resource.Reload)
|
if (string.IsNullOrEmpty(resource.RenderMode) || resource.RenderMode == RenderModes.Interactive)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(resource.Url))
|
if (!string.IsNullOrEmpty(resource.Url))
|
||||||
{
|
{
|
||||||
@ -82,6 +84,25 @@ namespace Oqtane.Themes
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
_scriptsloaded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ScriptsLoaded
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _scriptsloaded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// property for obtaining theme information about this theme component
|
||||||
|
public Theme ThemeState
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var type = GetType().Namespace + ", " + GetType().Assembly.GetName().Name;
|
||||||
|
return PageState?.Site.Themes.FirstOrDefault(item => item.ThemeName == type);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// path method
|
// path method
|
||||||
@ -91,6 +112,15 @@ namespace Oqtane.Themes
|
|||||||
return PageState?.Alias.BaseUrl + "/Themes/" + GetType().Namespace + "/";
|
return PageState?.Alias.BaseUrl + "/Themes/" + GetType().Namespace + "/";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fingerprint hash code for static assets
|
||||||
|
public string Fingerprint
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return ThemeState.Fingerprint;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// url methods
|
// url methods
|
||||||
|
|
||||||
// navigate url
|
// navigate url
|
||||||
|
@ -70,7 +70,7 @@
|
|||||||
if (!script.Contains("><") && !script.Contains("data-reload"))
|
if (!script.Contains("><") && !script.Contains("data-reload"))
|
||||||
{
|
{
|
||||||
// add data-reload attribute to inline script
|
// add data-reload attribute to inline script
|
||||||
headcontent = headcontent.Replace(script, script.Replace("<script", "<script data-reload=\"true\""));
|
headcontent = headcontent.Replace(script, script.Replace("<script", "<script data-reload=\"always\""));
|
||||||
}
|
}
|
||||||
index = headcontent.IndexOf("<script", index + 1);
|
index = headcontent.IndexOf("<script", index + 1);
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ using System.Threading.Tasks;
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
namespace Oqtane.UI
|
namespace Oqtane.UI
|
||||||
{
|
{
|
||||||
@ -36,6 +37,19 @@ namespace Oqtane.UI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task SetCookieString(string cookieString)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_jsRuntime.InvokeVoidAsync("Oqtane.Interop.setCookieString", cookieString);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public ValueTask<string> GetCookie(string name)
|
public ValueTask<string> GetCookie(string name)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@ -209,17 +223,22 @@ namespace Oqtane.UI
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Task UploadFiles(string posturl, string folder, string id, string antiforgerytoken, string jwt)
|
public Task UploadFiles(string posturl, string folder, string id, string antiforgerytoken, string jwt)
|
||||||
|
{
|
||||||
|
UploadFiles(posturl, folder, id, antiforgerytoken, jwt, 1);
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ValueTask<bool> UploadFiles(string posturl, string folder, string id, string antiforgerytoken, string jwt, int chunksize, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_jsRuntime.InvokeVoidAsync(
|
return _jsRuntime.InvokeAsync<bool>(
|
||||||
"Oqtane.Interop.uploadFiles",
|
"Oqtane.Interop.uploadFiles", cancellationToken,
|
||||||
posturl, folder, id, antiforgerytoken, jwt);
|
posturl, folder, id, antiforgerytoken, jwt, chunksize);
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
return Task.CompletedTask;
|
return new ValueTask<bool>(Task.FromResult(false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,6 +27,7 @@ namespace Oqtane.UI
|
|||||||
public bool IsInternalNavigation { get; set; }
|
public bool IsInternalNavigation { get; set; }
|
||||||
public Guid RenderId { get; set; }
|
public Guid RenderId { get; set; }
|
||||||
public bool Refresh { get; set; }
|
public bool Refresh { get; set; }
|
||||||
|
public bool AllowCookies { get; set; }
|
||||||
|
|
||||||
public List<Page> Pages
|
public List<Page> Pages
|
||||||
{
|
{
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
@inject IUrlMappingService UrlMappingService
|
@inject IUrlMappingService UrlMappingService
|
||||||
@inject ILogService LogService
|
@inject ILogService LogService
|
||||||
@inject ISettingService SettingService
|
@inject ISettingService SettingService
|
||||||
|
@inject ICookieConsentService CookieConsentService
|
||||||
@inject IJSRuntime JSRuntime
|
@inject IJSRuntime JSRuntime
|
||||||
@implements IHandleAfterRender
|
@implements IHandleAfterRender
|
||||||
@implements IDisposable
|
@implements IDisposable
|
||||||
@ -244,7 +245,9 @@
|
|||||||
// look for personalized page
|
// look for personalized page
|
||||||
if (user != null && page.IsPersonalizable && !UserSecurity.IsAuthorized(user, PermissionNames.Edit, page.PermissionList))
|
if (user != null && page.IsPersonalizable && !UserSecurity.IsAuthorized(user, PermissionNames.Edit, page.PermissionList))
|
||||||
{
|
{
|
||||||
var personalized = await PageService.GetPageAsync(route.PagePath + "/" + user.Username, site.SiteId);
|
var settingName = $"PersonalizedPagePath:{page.SiteId}:{page.PageId}";
|
||||||
|
var path = (user.Settings.ContainsKey(settingName)) ? user.Settings[settingName] : Utilities.GetFriendlyUrl(user.Username);
|
||||||
|
var personalized = await PageService.GetPageAsync(route.PagePath + "/" + path, site.SiteId);
|
||||||
if (personalized != null)
|
if (personalized != null)
|
||||||
{
|
{
|
||||||
// redirect to the personalized page
|
// redirect to the personalized page
|
||||||
@ -291,6 +294,14 @@
|
|||||||
// load additional metadata for modules
|
// load additional metadata for modules
|
||||||
(page, modules) = ProcessModules(site, page, modules, moduleid, action, (!string.IsNullOrEmpty(page.DefaultContainerType)) ? page.DefaultContainerType : site.DefaultContainerType, SiteState.Alias);
|
(page, modules) = ProcessModules(site, page, modules, moduleid, action, (!string.IsNullOrEmpty(page.DefaultContainerType)) ? page.DefaultContainerType : site.DefaultContainerType, SiteState.Alias);
|
||||||
|
|
||||||
|
//cookie consent
|
||||||
|
var _allowCookies = PageState?.AllowCookies;
|
||||||
|
if(!_allowCookies.HasValue)
|
||||||
|
{
|
||||||
|
var cookieConsentSettings = SettingService.GetSetting(site.Settings, "CookieConsent", string.Empty);
|
||||||
|
_allowCookies = string.IsNullOrEmpty(cookieConsentSettings) || await CookieConsentService.CanTrackAsync(cookieConsentSettings == "optout");
|
||||||
|
}
|
||||||
|
|
||||||
// populate page state (which acts as a client-side cache for subsequent requests)
|
// populate page state (which acts as a client-side cache for subsequent requests)
|
||||||
_pagestate = new PageState
|
_pagestate = new PageState
|
||||||
{
|
{
|
||||||
@ -314,7 +325,8 @@
|
|||||||
ReturnUrl = returnurl,
|
ReturnUrl = returnurl,
|
||||||
IsInternalNavigation = _isInternalNavigation,
|
IsInternalNavigation = _isInternalNavigation,
|
||||||
RenderId = Guid.NewGuid(),
|
RenderId = Guid.NewGuid(),
|
||||||
Refresh = false
|
Refresh = false,
|
||||||
|
AllowCookies = _allowCookies.GetValueOrDefault(true)
|
||||||
};
|
};
|
||||||
OnStateChange?.Invoke(_pagestate);
|
OnStateChange?.Invoke(_pagestate);
|
||||||
|
|
||||||
@ -389,7 +401,7 @@
|
|||||||
if (themetype != null)
|
if (themetype != null)
|
||||||
{
|
{
|
||||||
// get resources for theme (ITheme)
|
// get resources for theme (ITheme)
|
||||||
page.Resources = ManagePageResources(page.Resources, theme.Resources, ResourceLevel.Page, alias, "Themes", Utilities.GetTypeName(theme.ThemeName));
|
page.Resources = ManagePageResources(page.Resources, theme.Resources, ResourceLevel.Page, alias, "Themes", Utilities.GetTypeName(theme.ThemeName), theme.Fingerprint);
|
||||||
|
|
||||||
var themeobject = Activator.CreateInstance(themetype) as IThemeControl;
|
var themeobject = Activator.CreateInstance(themetype) as IThemeControl;
|
||||||
if (themeobject != null)
|
if (themeobject != null)
|
||||||
@ -399,7 +411,7 @@
|
|||||||
panes = themeobject.Panes;
|
panes = themeobject.Panes;
|
||||||
}
|
}
|
||||||
// get resources for theme control
|
// get resources for theme control
|
||||||
page.Resources = ManagePageResources(page.Resources, themeobject.Resources, ResourceLevel.Page, alias, "Themes", themetype.Namespace);
|
page.Resources = ManagePageResources(page.Resources, themeobject.Resources, ResourceLevel.Page, alias, "Themes", themetype.Namespace, theme.Fingerprint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// theme settings components are dynamically loaded within the framework Page Management module
|
// theme settings components are dynamically loaded within the framework Page Management module
|
||||||
@ -409,7 +421,7 @@
|
|||||||
if (settingsType != null)
|
if (settingsType != null)
|
||||||
{
|
{
|
||||||
var objSettings = Activator.CreateInstance(settingsType) as IModuleControl;
|
var objSettings = Activator.CreateInstance(settingsType) as IModuleControl;
|
||||||
page.Resources = ManagePageResources(page.Resources, objSettings.Resources, ResourceLevel.Module, alias, "Modules", settingsType.Namespace);
|
page.Resources = ManagePageResources(page.Resources, objSettings.Resources, ResourceLevel.Module, alias, "Modules", settingsType.Namespace, theme.Fingerprint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -453,7 +465,7 @@
|
|||||||
|
|
||||||
if (module.ModuleDefinition != null && (module.ModuleDefinition.Runtimes == "" || module.ModuleDefinition.Runtimes.Contains(Runtime)))
|
if (module.ModuleDefinition != null && (module.ModuleDefinition.Runtimes == "" || module.ModuleDefinition.Runtimes.Contains(Runtime)))
|
||||||
{
|
{
|
||||||
page.Resources = ManagePageResources(page.Resources, module.ModuleDefinition.Resources, ResourceLevel.Module, alias, "Modules", Utilities.GetTypeName(module.ModuleDefinition.ModuleDefinitionName));
|
page.Resources = ManagePageResources(page.Resources, module.ModuleDefinition.Resources, ResourceLevel.Module, alias, "Modules", Utilities.GetTypeName(module.ModuleDefinition.ModuleDefinitionName), module.ModuleDefinition.Fingerprint);
|
||||||
|
|
||||||
// handle default action
|
// handle default action
|
||||||
if (action == Constants.DefaultAction && !string.IsNullOrEmpty(module.ModuleDefinition.DefaultAction))
|
if (action == Constants.DefaultAction && !string.IsNullOrEmpty(module.ModuleDefinition.DefaultAction))
|
||||||
@ -502,7 +514,7 @@
|
|||||||
module.RenderMode = moduleobject.RenderMode;
|
module.RenderMode = moduleobject.RenderMode;
|
||||||
module.Prerender = moduleobject.Prerender;
|
module.Prerender = moduleobject.Prerender;
|
||||||
|
|
||||||
page.Resources = ManagePageResources(page.Resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace);
|
page.Resources = ManagePageResources(page.Resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace, module.ModuleDefinition?.Fingerprint);
|
||||||
|
|
||||||
// settings components are dynamically loaded within the framework Settings module
|
// settings components are dynamically loaded within the framework Settings module
|
||||||
if (action.ToLower() == "settings" && module.ModuleDefinition != null)
|
if (action.ToLower() == "settings" && module.ModuleDefinition != null)
|
||||||
@ -523,7 +535,7 @@
|
|||||||
if (moduletype != null)
|
if (moduletype != null)
|
||||||
{
|
{
|
||||||
moduleobject = Activator.CreateInstance(moduletype) as IModuleControl;
|
moduleobject = Activator.CreateInstance(moduletype) as IModuleControl;
|
||||||
page.Resources = ManagePageResources(page.Resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace);
|
page.Resources = ManagePageResources(page.Resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace, module.ModuleDefinition?.Fingerprint);
|
||||||
}
|
}
|
||||||
|
|
||||||
// container settings component
|
// container settings component
|
||||||
@ -534,7 +546,7 @@
|
|||||||
if (moduletype != null)
|
if (moduletype != null)
|
||||||
{
|
{
|
||||||
moduleobject = Activator.CreateInstance(moduletype) as IModuleControl;
|
moduleobject = Activator.CreateInstance(moduletype) as IModuleControl;
|
||||||
page.Resources = ManagePageResources(page.Resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace);
|
page.Resources = ManagePageResources(page.Resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace, theme.Fingerprint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -593,7 +605,7 @@
|
|||||||
return (page, modules);
|
return (page, modules);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Resource> ManagePageResources(List<Resource> pageresources, List<Resource> resources, ResourceLevel level, Alias alias, string type, string name)
|
private List<Resource> ManagePageResources(List<Resource> pageresources, List<Resource> resources, ResourceLevel level, Alias alias, string type, string name, string fingerprint)
|
||||||
{
|
{
|
||||||
if (resources != null)
|
if (resources != null)
|
||||||
{
|
{
|
||||||
@ -613,7 +625,7 @@
|
|||||||
// ensure resource does not exist already
|
// ensure resource does not exist already
|
||||||
if (!pageresources.Exists(item => item.Url.ToLower() == resource.Url.ToLower()))
|
if (!pageresources.Exists(item => item.Url.ToLower() == resource.Url.ToLower()))
|
||||||
{
|
{
|
||||||
pageresources.Add(resource.Clone(level, name));
|
pageresources.Add(resource.Clone(level, name, fingerprint));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,8 +11,6 @@
|
|||||||
|
|
||||||
RenderFragment DynamicComponent { get; set; }
|
RenderFragment DynamicComponent { get; set; }
|
||||||
|
|
||||||
private string lastPagePath = "";
|
|
||||||
|
|
||||||
protected override void OnParametersSet()
|
protected override void OnParametersSet()
|
||||||
{
|
{
|
||||||
// handle page redirection
|
// handle page redirection
|
||||||
@ -92,8 +90,9 @@
|
|||||||
|
|
||||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||||
{
|
{
|
||||||
if (!firstRender && PageState.Page.Path != lastPagePath)
|
if (!firstRender)
|
||||||
{
|
{
|
||||||
|
// site content
|
||||||
if (!string.IsNullOrEmpty(PageState.Site.HeadContent) && PageState.Site.HeadContent.Contains("<script"))
|
if (!string.IsNullOrEmpty(PageState.Site.HeadContent) && PageState.Site.HeadContent.Contains("<script"))
|
||||||
{
|
{
|
||||||
await InjectScripts(PageState.Site.HeadContent, ResourceLocation.Head);
|
await InjectScripts(PageState.Site.HeadContent, ResourceLocation.Head);
|
||||||
@ -102,6 +101,7 @@
|
|||||||
{
|
{
|
||||||
await InjectScripts(PageState.Site.BodyContent, ResourceLocation.Body);
|
await InjectScripts(PageState.Site.BodyContent, ResourceLocation.Body);
|
||||||
}
|
}
|
||||||
|
// page content
|
||||||
if (!string.IsNullOrEmpty(PageState.Page.HeadContent) && PageState.Page.HeadContent.Contains("<script"))
|
if (!string.IsNullOrEmpty(PageState.Page.HeadContent) && PageState.Page.HeadContent.Contains("<script"))
|
||||||
{
|
{
|
||||||
await InjectScripts(PageState.Page.HeadContent, ResourceLocation.Head);
|
await InjectScripts(PageState.Page.HeadContent, ResourceLocation.Head);
|
||||||
@ -110,7 +110,6 @@
|
|||||||
{
|
{
|
||||||
await InjectScripts(PageState.Page.BodyContent, ResourceLocation.Body);
|
await InjectScripts(PageState.Page.BodyContent, ResourceLocation.Body);
|
||||||
}
|
}
|
||||||
lastPagePath = PageState.Page.Path;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// style sheets
|
// style sheets
|
||||||
@ -190,8 +189,6 @@
|
|||||||
scripts.Add(new { href = src, type = type, bundle = "", integrity = integrity, crossorigin = crossorigin, location = location.ToString().ToLower(), dataAttributes = dataAttributes });
|
scripts.Add(new { href = src, type = type, bundle = "", integrity = integrity, crossorigin = crossorigin, location = location.ToString().ToLower(), dataAttributes = dataAttributes });
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
|
||||||
if (dataAttributes == null || !dataAttributes.ContainsKey("data-reload") || dataAttributes["data-reload"] != "false")
|
|
||||||
{
|
{
|
||||||
if (id == "")
|
if (id == "")
|
||||||
{
|
{
|
||||||
@ -201,7 +198,6 @@
|
|||||||
var pos = script.IndexOf(">") + 1;
|
var pos = script.IndexOf(">") + 1;
|
||||||
await interop.IncludeScript(id, "", "", "", type, script.Substring(pos, script.IndexOf("</script>") - pos), location.ToString().ToLower(), dataAttributes);
|
await interop.IncludeScript(id, "", "", "", type, script.Substring(pos, script.IndexOf("</script>") - pos), location.ToString().ToLower(), dataAttributes);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
index = content.IndexOf("<script", index + 1);
|
index = content.IndexOf("<script", index + 1);
|
||||||
}
|
}
|
||||||
if (scripts.Any())
|
if (scripts.Any())
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
using System.Data;
|
using System.Data;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
using Microsoft.EntityFrameworkCore.Migrations.Operations;
|
using Microsoft.EntityFrameworkCore.Migrations.Operations;
|
||||||
using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders;
|
using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders;
|
||||||
using MySql.Data.MySqlClient;
|
using MySql.Data.MySqlClient;
|
||||||
using MySql.EntityFrameworkCore.Metadata;
|
|
||||||
using Oqtane.Databases;
|
using Oqtane.Databases;
|
||||||
|
|
||||||
namespace Oqtane.Database.MySQL
|
namespace Oqtane.Database.MySQL
|
||||||
@ -21,11 +21,11 @@ namespace Oqtane.Database.MySQL
|
|||||||
|
|
||||||
public MySQLDatabase() :base(_name, _friendlyName) { }
|
public MySQLDatabase() :base(_name, _friendlyName) { }
|
||||||
|
|
||||||
public override string Provider => "MySql.EntityFrameworkCore";
|
public override string Provider => "Pomelo.EntityFrameworkCore.MySql";
|
||||||
|
|
||||||
public override OperationBuilder<AddColumnOperation> AddAutoIncrementColumn(ColumnsBuilder table, string name)
|
public override OperationBuilder<AddColumnOperation> AddAutoIncrementColumn(ColumnsBuilder table, string name)
|
||||||
{
|
{
|
||||||
return table.Column<int>(name: name, nullable: false).Annotation("MySQL:ValueGenerationStrategy", MySQLValueGenerationStrategy.IdentityColumn);
|
return table.Column<int>(name: name, nullable: false).Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ConcatenateSql(params string[] values)
|
public override string ConcatenateSql(params string[] values)
|
||||||
@ -86,7 +86,7 @@ namespace Oqtane.Database.MySQL
|
|||||||
|
|
||||||
public override DbContextOptionsBuilder UseDatabase(DbContextOptionsBuilder optionsBuilder, string connectionString)
|
public override DbContextOptionsBuilder UseDatabase(DbContextOptionsBuilder optionsBuilder, string connectionString)
|
||||||
{
|
{
|
||||||
return optionsBuilder.UseMySQL(connectionString);
|
return optionsBuilder.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PrepareCommand(MySqlConnection conn, MySqlCommand cmd, string query)
|
private void PrepareCommand(MySqlConnection conn, MySqlCommand cmd, string query)
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<Version>6.0.1</Version>
|
<Version>6.1.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/v6.0.1</PackageReleaseNotes>
|
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.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>
|
||||||
@ -33,8 +33,8 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="MySql.EntityFrameworkCore" Version="9.0.0-preview" />
|
<PackageReference Include="MySql.Data" Version="9.2.0" />
|
||||||
<PackageReference Include="MySql.Data" Version="9.1.0" />
|
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="9.0.0-preview.3.efcore.9.0.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
@ -42,7 +42,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<MySQLFiles Include="$(OutputPath)Oqtane.Database.MySQL.dll;$(OutputPath)Oqtane.Database.MySQL.pdb;$(OutputPath)MySql.EntityFrameworkCore.dll;$(OutputPath)MySql.Data.dll" DestinationPath="..\Oqtane.Server\bin\$(Configuration)\net9.0\%(Filename)%(Extension)" />
|
<MySQLFiles Include="$(OutputPath)Oqtane.Database.MySQL.dll;$(OutputPath)Oqtane.Database.MySQL.pdb;$(OutputPath)Pomelo.EntityFrameworkCore.MySql.dll;$(OutputPath)MySql.Data.dll" DestinationPath="..\Oqtane.Server\bin\$(Configuration)\net9.0\%(Filename)%(Extension)" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="PublishProvider" AfterTargets="PostBuildEvent" Inputs="@(MySQLFiles)" Outputs="@(MySQLFiles->'%(DestinationPath)')">
|
<Target Name="PublishProvider" AfterTargets="PostBuildEvent" Inputs="@(MySQLFiles)" Outputs="@(MySQLFiles->'%(DestinationPath)')">
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<Version>6.0.1</Version>
|
<Version>6.1.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/v6.0.1</PackageReleaseNotes>
|
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.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>
|
||||||
@ -34,8 +34,8 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="EFCore.NamingConventions" Version="9.0.0" />
|
<PackageReference Include="EFCore.NamingConventions" Version="9.0.0" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.0" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.3" />
|
||||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.1" />
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<Version>6.0.1</Version>
|
<Version>6.1.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/v6.0.1</PackageReleaseNotes>
|
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.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>
|
||||||
@ -33,7 +33,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.0" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<Version>6.0.1</Version>
|
<Version>6.1.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/v6.0.1</PackageReleaseNotes>
|
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.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>
|
||||||
@ -33,7 +33,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.0" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<!-- <TargetFrameworks>net9.0-android;net9.0-ios;net9.0-maccatalyst</TargetFrameworks> -->
|
<!-- <TargetFrameworks>net9.0-android;net9.0-ios;net9.0-maccatalyst</TargetFrameworks> -->
|
||||||
<!-- <TargetFrameworks>$(TargetFrameworks);net9.0-tizen</TargetFrameworks> -->
|
<!-- <TargetFrameworks>$(TargetFrameworks);net9.0-tizen</TargetFrameworks> -->
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<Version>6.0.1</Version>
|
<Version>6.1.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/v6.0.1</PackageReleaseNotes>
|
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.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>
|
||||||
@ -30,7 +30,7 @@
|
|||||||
<ApplicationId>com.oqtane.maui</ApplicationId>
|
<ApplicationId>com.oqtane.maui</ApplicationId>
|
||||||
|
|
||||||
<!-- Versions -->
|
<!-- Versions -->
|
||||||
<ApplicationDisplayVersion>6.0.1</ApplicationDisplayVersion>
|
<ApplicationDisplayVersion>6.1.1</ApplicationDisplayVersion>
|
||||||
<ApplicationVersion>1</ApplicationVersion>
|
<ApplicationVersion>1</ApplicationVersion>
|
||||||
|
|
||||||
<!-- To develop, package, and publish an app to the Microsoft Store, see: https://aka.ms/MauiTemplateUnpackaged -->
|
<!-- To develop, package, and publish an app to the Microsoft Store, see: https://aka.ms/MauiTemplateUnpackaged -->
|
||||||
@ -67,14 +67,14 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="9.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="9.0.3" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.3" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.3" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.3" />
|
||||||
<PackageReference Include="System.Net.Http.Json" Version="9.0.0" />
|
<PackageReference Include="System.Net.Http.Json" Version="9.0.3" />
|
||||||
<PackageReference Include="Microsoft.Maui.Controls" Version="9.0.0" />
|
<PackageReference Include="Microsoft.Maui.Controls" Version="9.0.40" />
|
||||||
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="9.0.0" />
|
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="9.0.40" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebView.Maui" Version="9.0.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebView.Maui" Version="9.0.40" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -14,6 +14,9 @@ Oqtane.Interop = {
|
|||||||
}
|
}
|
||||||
document.cookie = cookieString;
|
document.cookie = cookieString;
|
||||||
},
|
},
|
||||||
|
setCookieString: function (cookieString) {
|
||||||
|
document.cookie = cookieString;
|
||||||
|
},
|
||||||
getCookie: function (name) {
|
getCookie: function (name) {
|
||||||
name = name + "=";
|
name = name + "=";
|
||||||
var decodedCookie = decodeURIComponent(document.cookie);
|
var decodedCookie = decodeURIComponent(document.cookie);
|
||||||
@ -120,13 +123,22 @@ Oqtane.Interop = {
|
|||||||
this.includeLink(links[i].id, links[i].rel, links[i].href, links[i].type, links[i].integrity, links[i].crossorigin, links[i].insertbefore);
|
this.includeLink(links[i].id, links[i].rel, links[i].href, links[i].type, links[i].integrity, links[i].crossorigin, links[i].insertbefore);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
includeScript: function (id, src, integrity, crossorigin, type, content, location) {
|
includeScript: function (id, src, integrity, crossorigin, type, content, location, dataAttributes) {
|
||||||
var script;
|
var script;
|
||||||
if (src !== "") {
|
if (src !== "") {
|
||||||
script = document.querySelector("script[src=\"" + CSS.escape(src) + "\"]");
|
script = document.querySelector("script[src=\"" + CSS.escape(src) + "\"]");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
if (id !== "") {
|
||||||
script = document.getElementById(id);
|
script = document.getElementById(id);
|
||||||
|
} else {
|
||||||
|
const scripts = document.querySelectorAll("script:not([src])");
|
||||||
|
for (let i = 0; i < scripts.length; i++) {
|
||||||
|
if (scripts[i].textContent.includes(content)) {
|
||||||
|
script = scripts[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (script !== null) {
|
if (script !== null) {
|
||||||
script.remove();
|
script.remove();
|
||||||
@ -152,37 +164,36 @@ Oqtane.Interop = {
|
|||||||
else {
|
else {
|
||||||
script.innerHTML = content;
|
script.innerHTML = content;
|
||||||
}
|
}
|
||||||
script.async = false;
|
if (dataAttributes !== null) {
|
||||||
this.addScript(script, location)
|
for (var key in dataAttributes) {
|
||||||
.then(() => {
|
script.setAttribute(key, dataAttributes[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.addScript(script, location);
|
||||||
|
} catch (error) {
|
||||||
if (src !== "") {
|
if (src !== "") {
|
||||||
console.log(src + ' loaded');
|
console.error("Failed to load external script: ${src}", error);
|
||||||
|
} else {
|
||||||
|
console.error("Failed to load inline script: ${content}", error);
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
console.log(id + ' loaded');
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
if (src !== "") {
|
|
||||||
console.error(src + ' failed');
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
console.error(id + ' failed');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
addScript: function (script, location) {
|
addScript: function (script, location) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
script.async = false;
|
||||||
|
script.defer = false;
|
||||||
|
|
||||||
|
script.onload = () => resolve();
|
||||||
|
script.onerror = (error) => reject(error);
|
||||||
|
|
||||||
if (location === 'head') {
|
if (location === 'head') {
|
||||||
document.head.appendChild(script);
|
document.head.appendChild(script);
|
||||||
}
|
} else {
|
||||||
if (location === 'body') {
|
|
||||||
document.body.appendChild(script);
|
document.body.appendChild(script);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Promise((res, rej) => {
|
|
||||||
script.onload = res();
|
|
||||||
script.onerror = rej();
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
includeScripts: async function (scripts) {
|
includeScripts: async function (scripts) {
|
||||||
@ -222,10 +233,10 @@ Oqtane.Interop = {
|
|||||||
if (scripts[s].crossorigin !== '') {
|
if (scripts[s].crossorigin !== '') {
|
||||||
element.crossOrigin = scripts[s].crossorigin;
|
element.crossOrigin = scripts[s].crossorigin;
|
||||||
}
|
}
|
||||||
if (scripts[s].es6module === true) {
|
if (scripts[s].type !== '') {
|
||||||
element.type = "module";
|
element.type = scripts[s].type;
|
||||||
}
|
}
|
||||||
if (typeof scripts[s].dataAttributes !== "undefined" && scripts[s].dataAttributes !== null) {
|
if (scripts[s].dataAttributes !== null) {
|
||||||
for (var key in scripts[s].dataAttributes) {
|
for (var key in scripts[s].dataAttributes) {
|
||||||
element.setAttribute(key, scripts[s].dataAttributes[key]);
|
element.setAttribute(key, scripts[s].dataAttributes[key]);
|
||||||
}
|
}
|
||||||
@ -300,97 +311,107 @@ Oqtane.Interop = {
|
|||||||
}
|
}
|
||||||
return files;
|
return files;
|
||||||
},
|
},
|
||||||
uploadFiles: function (posturl, folder, id, antiforgerytoken, jwt) {
|
uploadFiles: async function (posturl, folder, id, antiforgerytoken, jwt, chunksize) {
|
||||||
|
var success = true;
|
||||||
var fileinput = document.getElementById('FileInput_' + id);
|
var fileinput = document.getElementById('FileInput_' + id);
|
||||||
var progressinfo = document.getElementById('ProgressInfo_' + id);
|
var progressinfo = document.getElementById('ProgressInfo_' + id);
|
||||||
var progressbar = document.getElementById('ProgressBar_' + id);
|
var progressbar = document.getElementById('ProgressBar_' + id);
|
||||||
|
|
||||||
|
var totalSize = 0;
|
||||||
|
for (var i = 0; i < fileinput.files.length; i++) {
|
||||||
|
totalSize += fileinput.files[i].size;
|
||||||
|
}
|
||||||
|
let uploadSize = 0;
|
||||||
|
|
||||||
|
if (!chunksize || chunksize < 1) {
|
||||||
|
chunksize = 1; // 1 MB default
|
||||||
|
}
|
||||||
|
|
||||||
if (progressinfo !== null && progressbar !== null) {
|
if (progressinfo !== null && progressbar !== null) {
|
||||||
progressinfo.setAttribute("style", "display: inline;");
|
progressinfo.setAttribute('style', 'display: inline;');
|
||||||
progressinfo.innerHTML = '';
|
if (fileinput.files.length > 1) {
|
||||||
progressbar.setAttribute("style", "width: 100%; display: inline;");
|
progressinfo.innerHTML = fileinput.files[0].name + ', ...';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
progressinfo.innerHTML = fileinput.files[0].name;
|
||||||
|
}
|
||||||
|
progressbar.setAttribute('style', 'width: 100%; display: inline;');
|
||||||
progressbar.value = 0;
|
progressbar.value = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
var files = fileinput.files;
|
const uploadFile = (file) => {
|
||||||
var totalSize = 0;
|
const chunkSize = chunksize * (1024 * 1024);
|
||||||
for (var i = 0; i < files.length; i++) {
|
const totalParts = Math.ceil(file.size / chunkSize);
|
||||||
totalSize = totalSize + files[i].size;
|
let partCount = 0;
|
||||||
}
|
|
||||||
|
|
||||||
var maxChunkSizeMB = 1;
|
const uploadPart = () => {
|
||||||
var bufferChunkSize = maxChunkSizeMB * (1024 * 1024);
|
const start = partCount * chunkSize;
|
||||||
var uploadedSize = 0;
|
const end = Math.min(start + chunkSize, file.size);
|
||||||
|
const chunk = file.slice(start, end);
|
||||||
|
|
||||||
for (var i = 0; i < files.length; i++) {
|
return new Promise((resolve, reject) => {
|
||||||
var fileChunk = [];
|
let formdata = new FormData();
|
||||||
var file = files[i];
|
formdata.append('__RequestVerificationToken', antiforgerytoken);
|
||||||
var fileStreamPos = 0;
|
formdata.append('folder', folder);
|
||||||
var endPos = bufferChunkSize;
|
formdata.append('formfile', chunk, file.name);
|
||||||
|
|
||||||
while (fileStreamPos < file.size) {
|
var credentials = 'same-origin';
|
||||||
fileChunk.push(file.slice(fileStreamPos, endPos));
|
var headers = new Headers();
|
||||||
fileStreamPos = endPos;
|
headers.append('PartCount', partCount + 1);
|
||||||
endPos = fileStreamPos + bufferChunkSize;
|
headers.append('TotalParts', totalParts);
|
||||||
}
|
|
||||||
|
|
||||||
var totalParts = fileChunk.length;
|
|
||||||
var partCount = 0;
|
|
||||||
|
|
||||||
while (chunk = fileChunk.shift()) {
|
|
||||||
partCount++;
|
|
||||||
var fileName = file.name + ".part_" + partCount.toString().padStart(3, '0') + "_" + totalParts.toString().padStart(3, '0');
|
|
||||||
|
|
||||||
var data = new FormData();
|
|
||||||
data.append('__RequestVerificationToken', antiforgerytoken);
|
|
||||||
data.append('folder', folder);
|
|
||||||
data.append('formfile', chunk, fileName);
|
|
||||||
var request = new XMLHttpRequest();
|
|
||||||
request.open('POST', posturl, true);
|
|
||||||
if (jwt !== "") {
|
if (jwt !== "") {
|
||||||
request.setRequestHeader('Authorization', 'Bearer ' + jwt);
|
headers.append('Authorization', 'Bearer ' + jwt);
|
||||||
request.withCredentials = true;
|
credentials = 'include';
|
||||||
}
|
}
|
||||||
request.upload.onloadstart = function (e) {
|
|
||||||
if (progressinfo !== null && progressbar !== null && progressinfo.innerHTML === '') {
|
return fetch(posturl, {
|
||||||
if (files.length === 1) {
|
method: 'POST',
|
||||||
progressinfo.innerHTML = file.name;
|
headers: headers,
|
||||||
}
|
credentials: credentials,
|
||||||
else {
|
body: formdata
|
||||||
progressinfo.innerHTML = file.name + ", ...";
|
})
|
||||||
}
|
.then(response => {
|
||||||
}
|
if (!response.ok) {
|
||||||
};
|
if (progressinfo !== null) {
|
||||||
request.upload.onprogress = function (e) {
|
progressinfo.innerHTML = 'Error: ' + response.statusText;
|
||||||
if (progressinfo !== null && progressbar !== null) {
|
}
|
||||||
var percent = Math.ceil(((uploadedSize + e.loaded) / totalSize) * 100);
|
throw new Error('Failed');
|
||||||
progressbar.value = (percent / 100);
|
}
|
||||||
}
|
return;
|
||||||
};
|
})
|
||||||
request.upload.onloadend = function (e) {
|
.then(data => {
|
||||||
if (progressinfo !== null && progressbar !== null) {
|
partCount++;
|
||||||
uploadedSize = uploadedSize + e.total;
|
if (progressbar !== null) {
|
||||||
var percent = Math.ceil((uploadedSize / totalSize) * 100);
|
uploadSize += chunk.size;
|
||||||
progressbar.value = (percent / 100);
|
var percent = Math.ceil((uploadSize / totalSize) * 100);
|
||||||
}
|
progressbar.value = (percent / 100);
|
||||||
};
|
}
|
||||||
request.upload.onerror = function() {
|
if (partCount < totalParts) {
|
||||||
if (progressinfo !== null && progressbar !== null) {
|
uploadPart().then(resolve).catch(reject);
|
||||||
if (files.length === 1) {
|
}
|
||||||
progressinfo.innerHTML = file.name + ' Error: ' + request.statusText;
|
else {
|
||||||
}
|
resolve(data);
|
||||||
else {
|
}
|
||||||
progressinfo.innerHTML = ' Error: ' + request.statusText;
|
})
|
||||||
}
|
.catch(error => {
|
||||||
}
|
reject(error);
|
||||||
};
|
});
|
||||||
request.send(data);
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return uploadPart();
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (const file of fileinput.files) {
|
||||||
|
await uploadFile(file);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
success = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i === files.length - 1) {
|
|
||||||
fileinput.value = '';
|
fileinput.value = '';
|
||||||
}
|
return success;
|
||||||
}
|
|
||||||
},
|
},
|
||||||
refreshBrowser: function (verify, wait) {
|
refreshBrowser: function (verify, wait) {
|
||||||
async function attemptReload (verify) {
|
async function attemptReload (verify) {
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<package>
|
<package>
|
||||||
<metadata>
|
<metadata>
|
||||||
<id>Oqtane.Client</id>
|
<id>Oqtane.Client</id>
|
||||||
<version>6.0.1</version>
|
<version>6.1.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/v6.0.1</releaseNotes>
|
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.1</releaseNotes>
|
||||||
<readme>readme.md</readme>
|
<readme>readme.md</readme>
|
||||||
<icon>icon.png</icon>
|
<icon>icon.png</icon>
|
||||||
<tags>oqtane</tags>
|
<tags>oqtane</tags>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<package>
|
<package>
|
||||||
<metadata>
|
<metadata>
|
||||||
<id>Oqtane.Framework</id>
|
<id>Oqtane.Framework</id>
|
||||||
<version>6.0.1</version>
|
<version>6.1.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/v6.0.1/Oqtane.Framework.6.0.1.Upgrade.zip</projectUrl>
|
<projectUrl>https://github.com/oqtane/oqtane.framework/releases/download/v6.1.1/Oqtane.Framework.6.1.1.Upgrade.zip</projectUrl>
|
||||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.1</releaseNotes>
|
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.1</releaseNotes>
|
||||||
<readme>readme.md</readme>
|
<readme>readme.md</readme>
|
||||||
<icon>icon.png</icon>
|
<icon>icon.png</icon>
|
||||||
<tags>oqtane framework</tags>
|
<tags>oqtane framework</tags>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<package>
|
<package>
|
||||||
<metadata>
|
<metadata>
|
||||||
<id>Oqtane.Server</id>
|
<id>Oqtane.Server</id>
|
||||||
<version>6.0.1</version>
|
<version>6.1.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/v6.0.1</releaseNotes>
|
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.1</releaseNotes>
|
||||||
<readme>readme.md</readme>
|
<readme>readme.md</readme>
|
||||||
<icon>icon.png</icon>
|
<icon>icon.png</icon>
|
||||||
<tags>oqtane</tags>
|
<tags>oqtane</tags>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<package>
|
<package>
|
||||||
<metadata>
|
<metadata>
|
||||||
<id>Oqtane.Shared</id>
|
<id>Oqtane.Shared</id>
|
||||||
<version>6.0.1</version>
|
<version>6.1.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/v6.0.1</releaseNotes>
|
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.1</releaseNotes>
|
||||||
<readme>readme.md</readme>
|
<readme>readme.md</readme>
|
||||||
<icon>icon.png</icon>
|
<icon>icon.png</icon>
|
||||||
<tags>oqtane</tags>
|
<tags>oqtane</tags>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<package>
|
<package>
|
||||||
<metadata>
|
<metadata>
|
||||||
<id>Oqtane.Updater</id>
|
<id>Oqtane.Updater</id>
|
||||||
<version>6.0.1</version>
|
<version>6.1.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/v6.0.1</releaseNotes>
|
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.1.1</releaseNotes>
|
||||||
<readme>readme.md</readme>
|
<readme>readme.md</readme>
|
||||||
<icon>icon.png</icon>
|
<icon>icon.png</icon>
|
||||||
<tags>oqtane</tags>
|
<tags>oqtane</tags>
|
||||||
|
@ -1 +1 @@
|
|||||||
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net9.0\publish\*" -DestinationPath "Oqtane.Framework.6.0.1.Install.zip" -Force
|
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net9.0\publish\*" -DestinationPath "Oqtane.Framework.6.1.1.Install.zip" -Force
|
||||||
|
@ -36,6 +36,7 @@ if "%%~nxi" == "%%j" set /A found=1
|
|||||||
)
|
)
|
||||||
if not !found! == 1 rmdir /Q/S "%%i"
|
if not !found! == 1 rmdir /Q/S "%%i"
|
||||||
)
|
)
|
||||||
|
del "..\Oqtane.Server\bin\Release\net9.0\publish\Oqtane.Server.staticwebassets.endpoints.json"
|
||||||
del "..\Oqtane.Server\bin\Release\net9.0\publish\appsettings.json"
|
del "..\Oqtane.Server\bin\Release\net9.0\publish\appsettings.json"
|
||||||
ren "..\Oqtane.Server\bin\Release\net9.0\publish\appsettings.release.json" "appsettings.json"
|
ren "..\Oqtane.Server\bin\Release\net9.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"
|
||||||
|
@ -1 +1 @@
|
|||||||
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net9.0\publish\*" -DestinationPath "Oqtane.Framework.6.0.1.Upgrade.zip" -Force
|
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net9.0\publish\*" -DestinationPath "Oqtane.Framework.6.1.1.Upgrade.zip" -Force
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
@using Microsoft.AspNetCore.Localization
|
@using Microsoft.AspNetCore.Localization
|
||||||
@using Microsoft.Net.Http.Headers
|
@using Microsoft.Net.Http.Headers
|
||||||
@using Microsoft.Extensions.Primitives
|
@using Microsoft.Extensions.Primitives
|
||||||
|
@using Microsoft.AspNetCore.Authentication
|
||||||
@using Oqtane.Client
|
@using Oqtane.Client
|
||||||
@using Oqtane.UI
|
@using Oqtane.UI
|
||||||
@using Oqtane.Repository
|
@using Oqtane.Repository
|
||||||
@ -30,6 +31,8 @@
|
|||||||
@inject IUrlMappingRepository UrlMappingRepository
|
@inject IUrlMappingRepository UrlMappingRepository
|
||||||
@inject IVisitorRepository VisitorRepository
|
@inject IVisitorRepository VisitorRepository
|
||||||
@inject IJwtManager JwtManager
|
@inject IJwtManager JwtManager
|
||||||
|
@inject ICookieConsentService CookieConsentService
|
||||||
|
@inject ISettingService SettingService
|
||||||
|
|
||||||
@if (_initialized)
|
@if (_initialized)
|
||||||
{
|
{
|
||||||
@ -39,7 +42,7 @@
|
|||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<base href="/" />
|
<base href="/" />
|
||||||
<link rel="stylesheet" href="css/app.css" />
|
<link rel="stylesheet" href="css/app.css?v=@_fingerprint" />
|
||||||
@if (_scripts.Contains("PWA Manifest"))
|
@if (_scripts.Contains("PWA Manifest"))
|
||||||
{
|
{
|
||||||
<link id="app-manifest" rel="manifest" />
|
<link id="app-manifest" rel="manifest" />
|
||||||
@ -70,15 +73,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
<script src="_framework/blazor.web.js"></script>
|
<script src="_framework/blazor.web.js"></script>
|
||||||
<script src="js/app.js"></script>
|
<script src="js/app.js?v=@_fingerprint"></script>
|
||||||
<script src="js/loadjs.min.js"></script>
|
<script src="js/loadjs.min.js?v=@_fingerprint"></script>
|
||||||
<script src="js/interop.js"></script>
|
<script src="js/interop.js?v=@_fingerprint"></script>
|
||||||
|
|
||||||
@((MarkupString)_scripts)
|
@((MarkupString)_scripts)
|
||||||
@((MarkupString)_bodyResources)
|
@((MarkupString)_bodyResources)
|
||||||
@if (_renderMode == RenderModes.Static)
|
@if (_renderMode == RenderModes.Static)
|
||||||
{
|
{
|
||||||
<page-script src="./js/reload.js"></page-script>
|
<page-script src="./js/reload.js?v=@_fingerprint"></page-script>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -94,6 +97,7 @@
|
|||||||
private string _renderMode = RenderModes.Interactive;
|
private string _renderMode = RenderModes.Interactive;
|
||||||
private string _runtime = Runtimes.Server;
|
private string _runtime = Runtimes.Server;
|
||||||
private bool _prerender = true;
|
private bool _prerender = true;
|
||||||
|
private string _fingerprint = "";
|
||||||
private int _visitorId = -1;
|
private int _visitorId = -1;
|
||||||
private string _antiForgeryToken = "";
|
private string _antiForgeryToken = "";
|
||||||
private string _remoteIPAddress = "";
|
private string _remoteIPAddress = "";
|
||||||
@ -105,6 +109,7 @@
|
|||||||
private string _styleSheets = "";
|
private string _styleSheets = "";
|
||||||
private string _scripts = "";
|
private string _scripts = "";
|
||||||
private string _message = "";
|
private string _message = "";
|
||||||
|
private bool _allowCookies;
|
||||||
private PageState _pageState;
|
private PageState _pageState;
|
||||||
|
|
||||||
// CascadingParameter is required to access HttpContext
|
// CascadingParameter is required to access HttpContext
|
||||||
@ -136,6 +141,11 @@
|
|||||||
_renderMode = site.RenderMode;
|
_renderMode = site.RenderMode;
|
||||||
_runtime = site.Runtime;
|
_runtime = site.Runtime;
|
||||||
_prerender = site.Prerender;
|
_prerender = site.Prerender;
|
||||||
|
_fingerprint = site.Fingerprint;
|
||||||
|
|
||||||
|
var cookieConsentSettings = SettingService.GetSetting(site.Settings, "CookieConsent", string.Empty);
|
||||||
|
_allowCookies = string.IsNullOrEmpty(cookieConsentSettings) || await CookieConsentService.CanTrackAsync(cookieConsentSettings == "optout");
|
||||||
|
|
||||||
var modules = new List<Module>();
|
var modules = new List<Module>();
|
||||||
|
|
||||||
Route route = new Route(url, alias.Path);
|
Route route = new Route(url, alias.Path);
|
||||||
@ -166,7 +176,7 @@
|
|||||||
modules = await SiteService.GetModulesAsync(site.SiteId, page.PageId);
|
modules = await SiteService.GetModulesAsync(site.SiteId, page.PageId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (site.VisitorTracking)
|
if (site.VisitorTracking && _allowCookies)
|
||||||
{
|
{
|
||||||
TrackVisitor(site.SiteId);
|
TrackVisitor(site.SiteId);
|
||||||
}
|
}
|
||||||
@ -174,7 +184,7 @@
|
|||||||
// get jwt token for downstream APIs
|
// get jwt token for downstream APIs
|
||||||
if (Context.User.Identity.IsAuthenticated)
|
if (Context.User.Identity.IsAuthenticated)
|
||||||
{
|
{
|
||||||
CreateJwtToken(alias);
|
await GetJwtToken(alias);
|
||||||
}
|
}
|
||||||
|
|
||||||
// includes resources
|
// includes resources
|
||||||
@ -241,7 +251,8 @@
|
|||||||
ReturnUrl = "",
|
ReturnUrl = "",
|
||||||
IsInternalNavigation = false,
|
IsInternalNavigation = false,
|
||||||
RenderId = Guid.NewGuid(),
|
RenderId = Guid.NewGuid(),
|
||||||
Refresh = true
|
Refresh = true,
|
||||||
|
AllowCookies = _allowCookies
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -441,8 +452,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CreateJwtToken(Alias alias)
|
private async Task GetJwtToken(Alias alias)
|
||||||
{
|
{
|
||||||
|
// bearer token may have been provided by remote Identity Provider and persisted using SaveTokens = true
|
||||||
|
_authorizationToken = await Context.GetTokenAsync("access_token");
|
||||||
|
if (string.IsNullOrEmpty(_authorizationToken))
|
||||||
|
{
|
||||||
|
// generate bearer token if a secret has been configured in User Settings
|
||||||
var sitesettings = Context.GetSiteSettings();
|
var sitesettings = Context.GetSiteSettings();
|
||||||
var secret = sitesettings.GetValue("JwtOptions:Secret", "");
|
var secret = sitesettings.GetValue("JwtOptions:Secret", "");
|
||||||
if (!string.IsNullOrEmpty(secret))
|
if (!string.IsNullOrEmpty(secret))
|
||||||
@ -450,6 +466,7 @@
|
|||||||
_authorizationToken = JwtManager.GenerateToken(alias, (ClaimsIdentity)Context.User.Identity, secret, sitesettings.GetValue("JwtOptions:Issuer", ""), sitesettings.GetValue("JwtOptions:Audience", ""), int.Parse(sitesettings.GetValue("JwtOptions:Lifetime", "20")));
|
_authorizationToken = JwtManager.GenerateToken(alias, (ClaimsIdentity)Context.User.Identity, secret, sitesettings.GetValue("JwtOptions:Issuer", ""), sitesettings.GetValue("JwtOptions:Audience", ""), int.Parse(sitesettings.GetValue("JwtOptions:Lifetime", "20")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private string CreatePWAScript(Alias alias, Site site, Route route)
|
private string CreatePWAScript(Alias alias, Site site, Route route)
|
||||||
{
|
{
|
||||||
@ -514,7 +531,7 @@
|
|||||||
private void AddScript(Resource resource, Alias alias)
|
private void AddScript(Resource resource, Alias alias)
|
||||||
{
|
{
|
||||||
var script = CreateScript(resource, alias);
|
var script = CreateScript(resource, alias);
|
||||||
if (resource.Location == Shared.ResourceLocation.Head && !resource.Reload)
|
if (resource.Location == Shared.ResourceLocation.Head && resource.LoadBehavior != ResourceLoadBehavior.BlazorPageScript)
|
||||||
{
|
{
|
||||||
if (!_headResources.Contains(script))
|
if (!_headResources.Contains(script))
|
||||||
{
|
{
|
||||||
@ -532,11 +549,27 @@
|
|||||||
|
|
||||||
private string CreateScript(Resource resource, Alias alias)
|
private string CreateScript(Resource resource, Alias alias)
|
||||||
{
|
{
|
||||||
if (!resource.Reload)
|
if (resource.LoadBehavior == ResourceLoadBehavior.BlazorPageScript)
|
||||||
|
{
|
||||||
|
return "<page-script src=\"" + resource.Url + "\"></page-script>";
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
var url = (resource.Url.Contains("://")) ? resource.Url : alias.BaseUrl + resource.Url;
|
var url = (resource.Url.Contains("://")) ? resource.Url : alias.BaseUrl + resource.Url;
|
||||||
|
|
||||||
var dataAttributes = "";
|
var dataAttributes = "";
|
||||||
|
if (!resource.DataAttributes.ContainsKey("data-reload"))
|
||||||
|
{
|
||||||
|
switch (resource.LoadBehavior)
|
||||||
|
{
|
||||||
|
case ResourceLoadBehavior.Once:
|
||||||
|
dataAttributes += " data-reload=\"once\"";
|
||||||
|
break;
|
||||||
|
case ResourceLoadBehavior.Always:
|
||||||
|
dataAttributes += " data-reload=\"always\"";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (resource.DataAttributes != null && resource.DataAttributes.Count > 0)
|
if (resource.DataAttributes != null && resource.DataAttributes.Count > 0)
|
||||||
{
|
{
|
||||||
foreach (var attribute in resource.DataAttributes)
|
foreach (var attribute in resource.DataAttributes)
|
||||||
@ -552,10 +585,6 @@
|
|||||||
((!string.IsNullOrEmpty(dataAttributes)) ? dataAttributes : "") +
|
((!string.IsNullOrEmpty(dataAttributes)) ? dataAttributes : "") +
|
||||||
"></script>";
|
"></script>";
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
return "<page-script src=\"" + resource.Url + "\"></page-script>";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetLocalizationCookie(string cookieValue)
|
private void SetLocalizationCookie(string cookieValue)
|
||||||
@ -583,13 +612,13 @@
|
|||||||
var theme = site.Themes.FirstOrDefault(item => item.Themes.Any(item => item.TypeName == themeType));
|
var theme = site.Themes.FirstOrDefault(item => item.Themes.Any(item => item.TypeName == themeType));
|
||||||
if (theme != null)
|
if (theme != null)
|
||||||
{
|
{
|
||||||
resources = AddResources(resources, theme.Resources, ResourceLevel.Page, alias, "Themes", Utilities.GetTypeName(theme.ThemeName), site.RenderMode);
|
resources = AddResources(resources, theme.Resources, ResourceLevel.Page, alias, "Themes", Utilities.GetTypeName(theme.ThemeName), theme.Fingerprint, site.RenderMode);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// fallback to default Oqtane theme
|
// fallback to default Oqtane theme
|
||||||
theme = site.Themes.FirstOrDefault(item => item.Themes.Any(item => item.TypeName == Constants.DefaultTheme));
|
theme = site.Themes.FirstOrDefault(item => item.Themes.Any(item => item.TypeName == Constants.DefaultTheme));
|
||||||
resources = AddResources(resources, theme.Resources, ResourceLevel.Page, alias, "Themes", Utilities.GetTypeName(theme.ThemeName), site.RenderMode);
|
resources = AddResources(resources, theme.Resources, ResourceLevel.Page, alias, "Themes", Utilities.GetTypeName(theme.ThemeName), theme.Fingerprint, site.RenderMode);
|
||||||
}
|
}
|
||||||
var type = Type.GetType(themeType);
|
var type = Type.GetType(themeType);
|
||||||
if (type != null)
|
if (type != null)
|
||||||
@ -597,7 +626,7 @@
|
|||||||
var obj = Activator.CreateInstance(type) as IThemeControl;
|
var obj = Activator.CreateInstance(type) as IThemeControl;
|
||||||
if (obj != null)
|
if (obj != null)
|
||||||
{
|
{
|
||||||
resources = AddResources(resources, obj.Resources, ResourceLevel.Page, alias, "Themes", type.Namespace, site.RenderMode);
|
resources = AddResources(resources, obj.Resources, ResourceLevel.Page, alias, "Themes", type.Namespace, theme.Fingerprint, site.RenderMode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// theme settings components are dynamically loaded within the framework Page Management module
|
// theme settings components are dynamically loaded within the framework Page Management module
|
||||||
@ -607,7 +636,7 @@
|
|||||||
if (settingsType != null)
|
if (settingsType != null)
|
||||||
{
|
{
|
||||||
var objSettings = Activator.CreateInstance(settingsType) as IModuleControl;
|
var objSettings = Activator.CreateInstance(settingsType) as IModuleControl;
|
||||||
resources = AddResources(resources, objSettings.Resources, ResourceLevel.Module, alias, "Modules", settingsType.Namespace, site.RenderMode);
|
resources = AddResources(resources, objSettings.Resources, ResourceLevel.Module, alias, "Modules", settingsType.Namespace, theme.Fingerprint, site.RenderMode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -616,7 +645,7 @@
|
|||||||
var typename = "";
|
var typename = "";
|
||||||
if (module.ModuleDefinition != null)
|
if (module.ModuleDefinition != null)
|
||||||
{
|
{
|
||||||
resources = AddResources(resources, module.ModuleDefinition.Resources, ResourceLevel.Module, alias, "Modules", Utilities.GetTypeName(module.ModuleDefinition.ModuleDefinitionName), site.RenderMode);
|
resources = AddResources(resources, module.ModuleDefinition.Resources, ResourceLevel.Module, alias, "Modules", Utilities.GetTypeName(module.ModuleDefinition.ModuleDefinitionName), module.ModuleDefinition.Fingerprint, site.RenderMode);
|
||||||
|
|
||||||
// handle default action
|
// handle default action
|
||||||
if (action == Constants.DefaultAction && !string.IsNullOrEmpty(module.ModuleDefinition.DefaultAction))
|
if (action == Constants.DefaultAction && !string.IsNullOrEmpty(module.ModuleDefinition.DefaultAction))
|
||||||
@ -662,7 +691,7 @@
|
|||||||
var moduleobject = Activator.CreateInstance(moduletype) as IModuleControl;
|
var moduleobject = Activator.CreateInstance(moduletype) as IModuleControl;
|
||||||
if (moduleobject != null)
|
if (moduleobject != null)
|
||||||
{
|
{
|
||||||
resources = AddResources(resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace, site.RenderMode);
|
resources = AddResources(resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace, module.ModuleDefinition?.Fingerprint, site.RenderMode);
|
||||||
|
|
||||||
// settings components are dynamically loaded within the framework Settings module
|
// settings components are dynamically loaded within the framework Settings module
|
||||||
if (action.ToLower() == "settings" && module.ModuleDefinition != null)
|
if (action.ToLower() == "settings" && module.ModuleDefinition != null)
|
||||||
@ -683,7 +712,7 @@
|
|||||||
if (moduletype != null)
|
if (moduletype != null)
|
||||||
{
|
{
|
||||||
moduleobject = Activator.CreateInstance(moduletype) as IModuleControl;
|
moduleobject = Activator.CreateInstance(moduletype) as IModuleControl;
|
||||||
resources = AddResources(resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace, site.RenderMode);
|
resources = AddResources(resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace, module.ModuleDefinition?.Fingerprint, site.RenderMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
// container settings component
|
// container settings component
|
||||||
@ -693,7 +722,7 @@
|
|||||||
if (moduletype != null)
|
if (moduletype != null)
|
||||||
{
|
{
|
||||||
moduleobject = Activator.CreateInstance(moduletype) as IModuleControl;
|
moduleobject = Activator.CreateInstance(moduletype) as IModuleControl;
|
||||||
resources = AddResources(resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace, site.RenderMode);
|
resources = AddResources(resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace, theme.Fingerprint, site.RenderMode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -709,7 +738,7 @@
|
|||||||
{
|
{
|
||||||
if (module.ModuleDefinition?.Resources != null)
|
if (module.ModuleDefinition?.Resources != null)
|
||||||
{
|
{
|
||||||
resources = AddResources(resources, module.ModuleDefinition.Resources.Where(item => item.ResourceType == ResourceType.Script && item.Level == ResourceLevel.Site).ToList(), ResourceLevel.Module, alias, "Modules", Utilities.GetTypeName(module.ModuleDefinition.ModuleDefinitionName), site.RenderMode);
|
resources = AddResources(resources, module.ModuleDefinition.Resources.Where(item => item.ResourceType == ResourceType.Script && item.Level == ResourceLevel.Site).ToList(), ResourceLevel.Module, alias, "Modules", Utilities.GetTypeName(module.ModuleDefinition.ModuleDefinitionName), module.ModuleDefinition.Fingerprint, site.RenderMode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -717,7 +746,7 @@
|
|||||||
return resources;
|
return resources;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Resource> AddResources(List<Resource> pageresources, List<Resource> resources, ResourceLevel level, Alias alias, string type, string name, string rendermode)
|
private List<Resource> AddResources(List<Resource> pageresources, List<Resource> resources, ResourceLevel level, Alias alias, string type, string name, string fingerprint, string rendermode)
|
||||||
{
|
{
|
||||||
if (resources != null)
|
if (resources != null)
|
||||||
{
|
{
|
||||||
@ -737,7 +766,7 @@
|
|||||||
// ensure resource does not exist already
|
// ensure resource does not exist already
|
||||||
if (!pageresources.Exists(item => item.Url.ToLower() == resource.Url.ToLower()))
|
if (!pageresources.Exists(item => item.Url.ToLower() == resource.Url.ToLower()))
|
||||||
{
|
{
|
||||||
pageresources.Add(resource.Clone(level, name));
|
pageresources.Add(resource.Clone(level, name, fingerprint));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
52
Oqtane.Server/Controllers/CookieConsentController.cs
Normal file
52
Oqtane.Server/Controllers/CookieConsentController.cs
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Oqtane.Models;
|
||||||
|
using Oqtane.Shared;
|
||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using Oqtane.Infrastructure;
|
||||||
|
using Oqtane.Services;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Oqtane.Controllers
|
||||||
|
{
|
||||||
|
[Route(ControllerRoutes.ApiRoute)]
|
||||||
|
public class CookieConsentController : Controller
|
||||||
|
{
|
||||||
|
private readonly ICookieConsentService _cookieConsentService;
|
||||||
|
|
||||||
|
public CookieConsentController(ICookieConsentService cookieConsentService)
|
||||||
|
{
|
||||||
|
_cookieConsentService = cookieConsentService;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("IsActioned")]
|
||||||
|
public async Task<bool> IsActioned()
|
||||||
|
{
|
||||||
|
return await _cookieConsentService.IsActionedAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("CanTrack")]
|
||||||
|
public async Task<bool> CanTrack(string optout)
|
||||||
|
{
|
||||||
|
return await _cookieConsentService.CanTrackAsync(bool.Parse(optout));
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("CreateActionedCookie")]
|
||||||
|
public async Task<string> CreateActionedCookie()
|
||||||
|
{
|
||||||
|
return await _cookieConsentService.CreateActionedCookieAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("CreateConsentCookie")]
|
||||||
|
public async Task<string> CreateConsentCookie()
|
||||||
|
{
|
||||||
|
return await _cookieConsentService.CreateConsentCookieAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("WithdrawConsentCookie")]
|
||||||
|
public async Task<string> WithdrawConsentCookie()
|
||||||
|
{
|
||||||
|
return await _cookieConsentService.WithdrawConsentCookieAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
56
Oqtane.Server/Controllers/EndpointController.cs
Normal file
56
Oqtane.Server/Controllers/EndpointController.cs
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Oqtane.Models;
|
||||||
|
using Oqtane.Shared;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||||
|
using Microsoft.AspNetCore.Routing;
|
||||||
|
|
||||||
|
namespace Oqtane.Controllers
|
||||||
|
{
|
||||||
|
[Route(ControllerRoutes.ApiRoute)]
|
||||||
|
public class EndpointController : Controller
|
||||||
|
{
|
||||||
|
private readonly IEnumerable<EndpointDataSource> _endpointSources;
|
||||||
|
|
||||||
|
public EndpointController(IEnumerable<EndpointDataSource> endpointSources)
|
||||||
|
{
|
||||||
|
_endpointSources = endpointSources;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET api/<controller>
|
||||||
|
[HttpGet]
|
||||||
|
[Authorize(Roles = RoleNames.Host)]
|
||||||
|
public ActionResult Get()
|
||||||
|
{
|
||||||
|
var endpoints = _endpointSources
|
||||||
|
.SelectMany(item => item.Endpoints)
|
||||||
|
.OfType<RouteEndpoint>();
|
||||||
|
|
||||||
|
var output = endpoints.Select(
|
||||||
|
item =>
|
||||||
|
{
|
||||||
|
var controller = item.Metadata
|
||||||
|
.OfType<ControllerActionDescriptor>()
|
||||||
|
.FirstOrDefault();
|
||||||
|
var action = controller != null
|
||||||
|
? $"{controller.ControllerName}.{controller.ActionName}"
|
||||||
|
: null;
|
||||||
|
var controllerMethod = controller != null
|
||||||
|
? $"{controller.ControllerTypeInfo.FullName}:{controller.MethodInfo.Name}"
|
||||||
|
: null;
|
||||||
|
return new
|
||||||
|
{
|
||||||
|
Method = item.Metadata.OfType<HttpMethodMetadata>().FirstOrDefault()?.HttpMethods?[0],
|
||||||
|
Route = $"/{item.RoutePattern.RawText.TrimStart('/')}",
|
||||||
|
Action = action,
|
||||||
|
ControllerMethod = controllerMethod
|
||||||
|
};
|
||||||
|
}
|
||||||
|
).OrderBy(item => item.Route);
|
||||||
|
|
||||||
|
return Json(output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -21,6 +21,8 @@ using System.Net.Http;
|
|||||||
using Microsoft.AspNetCore.Cors;
|
using Microsoft.AspNetCore.Cors;
|
||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
using Oqtane.Services;
|
using Oqtane.Services;
|
||||||
|
using Microsoft.Extensions.Primitives;
|
||||||
|
using Microsoft.AspNetCore.Http.HttpResults;
|
||||||
|
|
||||||
// ReSharper disable StringIndexOfIsCultureSpecific.1
|
// ReSharper disable StringIndexOfIsCultureSpecific.1
|
||||||
|
|
||||||
@ -427,23 +429,41 @@ namespace Oqtane.Controllers
|
|||||||
// POST api/<controller>/upload
|
// POST api/<controller>/upload
|
||||||
[EnableCors(Constants.MauiCorsPolicy)]
|
[EnableCors(Constants.MauiCorsPolicy)]
|
||||||
[HttpPost("upload")]
|
[HttpPost("upload")]
|
||||||
public async Task<IActionResult> UploadFile(string folder, IFormFile formfile)
|
public async Task<IActionResult> UploadFile([FromForm] string folder, IFormFile formfile)
|
||||||
{
|
{
|
||||||
|
if (string.IsNullOrEmpty(folder))
|
||||||
|
{
|
||||||
|
_logger.Log(LogLevel.Error, this, LogFunction.Security, "File Upload Does Not Contain A Folder");
|
||||||
|
return StatusCode((int)HttpStatusCode.Forbidden);
|
||||||
|
}
|
||||||
|
|
||||||
if (formfile == null || formfile.Length <= 0)
|
if (formfile == null || formfile.Length <= 0)
|
||||||
{
|
{
|
||||||
return NoContent();
|
_logger.Log(LogLevel.Error, this, LogFunction.Security, "File Upload Does Not Contain A File");
|
||||||
|
return StatusCode((int)HttpStatusCode.Forbidden);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ensure filename is valid
|
// ensure filename is valid
|
||||||
string token = ".part_";
|
if (!formfile.FileName.IsPathOrFileValid() || !HasValidFileExtension(formfile.FileName))
|
||||||
if (!formfile.FileName.IsPathOrFileValid() || !formfile.FileName.Contains(token) || !HasValidFileExtension(formfile.FileName.Substring(0, formfile.FileName.IndexOf(token))))
|
|
||||||
{
|
{
|
||||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "File Name Is Invalid Or Contains Invalid Extension {File}", formfile.FileName);
|
_logger.Log(LogLevel.Error, this, LogFunction.Security, "File Upload File Name Is Invalid Or Contains Invalid Extension {File}", formfile.FileName);
|
||||||
return NoContent();
|
return StatusCode((int)HttpStatusCode.Forbidden);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ensure headers exist
|
||||||
|
if (!Request.Headers.TryGetValue("PartCount", out StringValues partcount) || !int.TryParse(partcount, out int partCount) || partCount <= 0 ||
|
||||||
|
!Request.Headers.TryGetValue("TotalParts", out StringValues totalparts) || !int.TryParse(totalparts, out int totalParts) || totalParts <= 0)
|
||||||
|
{
|
||||||
|
_logger.Log(LogLevel.Error, this, LogFunction.Security, "File Upload Is Missing Required Headers");
|
||||||
|
return StatusCode((int)HttpStatusCode.Forbidden);
|
||||||
|
}
|
||||||
|
|
||||||
|
// create file name using header values
|
||||||
|
string fileName = formfile.FileName + ".part_" + partCount.ToString("000") + "_" + totalParts.ToString("000");
|
||||||
string folderPath = "";
|
string folderPath = "";
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
int FolderId;
|
int FolderId;
|
||||||
if (int.TryParse(folder, out FolderId))
|
if (int.TryParse(folder, out FolderId))
|
||||||
{
|
{
|
||||||
@ -465,12 +485,12 @@ namespace Oqtane.Controllers
|
|||||||
if (!string.IsNullOrEmpty(folderPath))
|
if (!string.IsNullOrEmpty(folderPath))
|
||||||
{
|
{
|
||||||
CreateDirectory(folderPath);
|
CreateDirectory(folderPath);
|
||||||
using (var stream = new FileStream(Path.Combine(folderPath, formfile.FileName), FileMode.Create))
|
using (var stream = new FileStream(Path.Combine(folderPath, fileName), FileMode.Create))
|
||||||
{
|
{
|
||||||
await formfile.CopyToAsync(stream);
|
await formfile.CopyToAsync(stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
string upload = await MergeFile(folderPath, formfile.FileName);
|
string upload = await MergeFile(folderPath, fileName);
|
||||||
if (upload != "" && FolderId != -1)
|
if (upload != "" && FolderId != -1)
|
||||||
{
|
{
|
||||||
var file = CreateFile(upload, FolderId, Path.Combine(folderPath, upload));
|
var file = CreateFile(upload, FolderId, Path.Combine(folderPath, upload));
|
||||||
@ -488,14 +508,19 @@ namespace Oqtane.Controllers
|
|||||||
_syncManager.AddSyncEvent(_alias, EntityNames.File, file.FileId, SyncEventActions.Create);
|
_syncManager.AddSyncEvent(_alias, EntityNames.File, file.FileId, SyncEventActions.Create);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return NoContent();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized File Upload Attempt {Folder} {File}", folder, formfile.FileName);
|
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized File Upload Attempt {Folder} {File}", folder, formfile.FileName);
|
||||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
return StatusCode((int)HttpStatusCode.Forbidden);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.Log(LogLevel.Error, this, LogFunction.Create, ex, "File Upload Attempt Failed {Folder} {File}", folder, formfile.FileName);
|
||||||
|
return StatusCode((int)HttpStatusCode.InternalServerError);
|
||||||
}
|
}
|
||||||
|
|
||||||
return NoContent();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<string> MergeFile(string folder, string filename)
|
private async Task<string> MergeFile(string folder, string filename)
|
||||||
@ -536,17 +561,23 @@ namespace Oqtane.Controllers
|
|||||||
// clean up file parts
|
// clean up file parts
|
||||||
foreach (var file in Directory.GetFiles(folder, "*" + token + "*"))
|
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))
|
||||||
if (fileparts.Contains(file) || System.IO.File.GetCreationTime(file).ToUniversalTime() < DateTime.UtcNow.AddHours(-2))
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
System.IO.File.Delete(file);
|
System.IO.File.Delete(file);
|
||||||
}
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// unable to delete part - ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// rename temp file
|
// rename temp file
|
||||||
if (success)
|
if (success)
|
||||||
{
|
{
|
||||||
// remove file if it already exists (as well as any thumbnails)
|
// remove file if it already exists (as well as any thumbnails which may exist)
|
||||||
foreach (var file in Directory.GetFiles(folder, Path.GetFileNameWithoutExtension(filename) + ".*"))
|
foreach (var file in Directory.GetFiles(folder, Path.GetFileNameWithoutExtension(filename) + ".*"))
|
||||||
{
|
{
|
||||||
if (Path.GetExtension(file) != ".tmp")
|
if (Path.GetExtension(file) != ".tmp")
|
||||||
|
@ -60,9 +60,9 @@ namespace Oqtane.Controllers
|
|||||||
{
|
{
|
||||||
installation = _databaseManager.Install(config);
|
installation = _databaseManager.Install(config);
|
||||||
|
|
||||||
if (installation.Success && config.Register)
|
if (installation.Success)
|
||||||
{
|
{
|
||||||
await RegisterContact(config.HostEmail);
|
await RegisterContact(config.HostEmail, config.HostName, config.Register);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -171,7 +171,8 @@ namespace Oqtane.Controllers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return assemblyList;
|
return assemblyList;
|
||||||
});
|
}).ToList();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET api/<controller>/load?list=x,y
|
// GET api/<controller>/load?list=x,y
|
||||||
@ -257,7 +258,7 @@ namespace Oqtane.Controllers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task RegisterContact(string email)
|
private async Task RegisterContact(string email, string name, bool register)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -268,7 +269,7 @@ namespace Oqtane.Controllers
|
|||||||
{
|
{
|
||||||
client.DefaultRequestHeaders.Add("Referer", HttpContext.Request.Scheme + "://" + HttpContext.Request.Host.Value);
|
client.DefaultRequestHeaders.Add("Referer", HttpContext.Request.Scheme + "://" + HttpContext.Request.Host.Value);
|
||||||
client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(Constants.PackageId, Constants.Version));
|
client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(Constants.PackageId, Constants.Version));
|
||||||
var response = await client.GetAsync(new Uri(url + $"/api/registry/contact/?id={_configManager.GetInstallationId()}&email={WebUtility.UrlEncode(email)}")).ConfigureAwait(false);
|
var response = await client.GetAsync(new Uri(url + $"/api/registry/contact/?id={_configManager.GetInstallationId()}&email={WebUtility.UrlEncode(email)}&name={WebUtility.UrlEncode(name)}®ister={register.ToString().ToLower()}")).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -278,14 +279,6 @@ namespace Oqtane.Controllers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET api/<controller>/register?email=x
|
|
||||||
[HttpPost("register")]
|
|
||||||
[Authorize(Roles = RoleNames.Host)]
|
|
||||||
public async Task Register(string email)
|
|
||||||
{
|
|
||||||
await RegisterContact(email);
|
|
||||||
}
|
|
||||||
|
|
||||||
public struct ClientAssembly
|
public struct ClientAssembly
|
||||||
{
|
{
|
||||||
public ClientAssembly(string filepath, bool hashfilename)
|
public ClientAssembly(string filepath, bool hashfilename)
|
||||||
@ -294,7 +287,7 @@ namespace Oqtane.Controllers
|
|||||||
DateTime lastwritetime = System.IO.File.GetLastWriteTime(filepath);
|
DateTime lastwritetime = System.IO.File.GetLastWriteTime(filepath);
|
||||||
if (hashfilename)
|
if (hashfilename)
|
||||||
{
|
{
|
||||||
HashedName = GetDeterministicHashCode(filepath).ToString("X8") + "." + lastwritetime.ToString("yyyyMMddHHmmss") + Path.GetExtension(filepath);
|
HashedName = Utilities.GenerateSimpleHash(filepath) + "." + lastwritetime.ToString("yyyyMMddHHmmss") + Path.GetExtension(filepath);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -305,25 +298,5 @@ namespace Oqtane.Controllers
|
|||||||
public string FilePath { get; private set; }
|
public string FilePath { get; private set; }
|
||||||
public string HashedName { get; private set; }
|
public string HashedName { get; private set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int GetDeterministicHashCode(string value)
|
|
||||||
{
|
|
||||||
unchecked
|
|
||||||
{
|
|
||||||
int hash1 = (5381 << 16) + 5381;
|
|
||||||
int hash2 = hash1;
|
|
||||||
|
|
||||||
for (int i = 0; i < value.Length; i += 2)
|
|
||||||
{
|
|
||||||
hash1 = ((hash1 << 5) + hash1) ^ value[i];
|
|
||||||
if (i == value.Length - 1)
|
|
||||||
break;
|
|
||||||
hash2 = ((hash2 << 5) + hash2) ^ value[i + 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
return hash1 + (hash2 * 1566083941);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -155,7 +155,7 @@ namespace Oqtane.Controllers
|
|||||||
[Authorize(Roles = RoleNames.Registered)]
|
[Authorize(Roles = RoleNames.Registered)]
|
||||||
public Notification Post([FromBody] Notification notification)
|
public Notification Post([FromBody] Notification notification)
|
||||||
{
|
{
|
||||||
if (ModelState.IsValid && notification.SiteId == _alias.SiteId && IsAuthorized(notification.FromUserId))
|
if (ModelState.IsValid && notification.SiteId == _alias.SiteId && (IsAuthorized(notification.FromUserId) || (notification.FromUserId == null && User.IsInRole(RoleNames.Admin))))
|
||||||
{
|
{
|
||||||
if (!User.IsInRole(RoleNames.Admin))
|
if (!User.IsInRole(RoleNames.Admin))
|
||||||
{
|
{
|
||||||
@ -181,14 +181,35 @@ namespace Oqtane.Controllers
|
|||||||
[Authorize(Roles = RoleNames.Registered)]
|
[Authorize(Roles = RoleNames.Registered)]
|
||||||
public Notification Put(int id, [FromBody] Notification notification)
|
public Notification Put(int id, [FromBody] Notification notification)
|
||||||
{
|
{
|
||||||
if (ModelState.IsValid && notification.SiteId == _alias.SiteId && notification.NotificationId == id && _notifications.GetNotification(notification.NotificationId, false) != null && (IsAuthorized(notification.FromUserId) || IsAuthorized(notification.ToUserId)))
|
if (ModelState.IsValid && notification.SiteId == _alias.SiteId && notification.NotificationId == id && _notifications.GetNotification(notification.NotificationId, false) != null)
|
||||||
{
|
{
|
||||||
if (!User.IsInRole(RoleNames.Admin) && notification.FromUserId != null)
|
bool update = false;
|
||||||
|
if (IsAuthorized(notification.FromUserId))
|
||||||
|
{
|
||||||
|
// notification belongs to current authenticated user - update is allowed
|
||||||
|
if (!User.IsInRole(RoleNames.Admin))
|
||||||
{
|
{
|
||||||
// content must be HTML encoded for non-admins to prevent HTML injection
|
// content must be HTML encoded for non-admins to prevent HTML injection
|
||||||
notification.Subject = WebUtility.HtmlEncode(notification.Subject);
|
notification.Subject = WebUtility.HtmlEncode(notification.Subject);
|
||||||
notification.Body = WebUtility.HtmlEncode(notification.Body);
|
notification.Body = WebUtility.HtmlEncode(notification.Body);
|
||||||
}
|
}
|
||||||
|
update = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (IsAuthorized(notification.ToUserId))
|
||||||
|
{
|
||||||
|
// notification was sent to current authenticated user - only isread and isdeleted properties can be updated
|
||||||
|
var isread = notification.IsRead;
|
||||||
|
var isdeleted = notification.IsDeleted;
|
||||||
|
notification = _notifications.GetNotification(notification.NotificationId);
|
||||||
|
notification.IsRead = isread;
|
||||||
|
notification.IsDeleted = isdeleted;
|
||||||
|
update = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (update)
|
||||||
|
{
|
||||||
notification = _notifications.UpdateNotification(notification);
|
notification = _notifications.UpdateNotification(notification);
|
||||||
_syncManager.AddSyncEvent(_alias, EntityNames.Notification, notification.NotificationId, SyncEventActions.Update);
|
_syncManager.AddSyncEvent(_alias, EntityNames.Notification, notification.NotificationId, SyncEventActions.Update);
|
||||||
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Notification Updated {NotificationId}", notification.NotificationId);
|
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Notification Updated {NotificationId}", notification.NotificationId);
|
||||||
@ -199,6 +220,13 @@ namespace Oqtane.Controllers
|
|||||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||||
notification = null;
|
notification = null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Notification Put Attempt {Notification}", notification);
|
||||||
|
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||||
|
notification = null;
|
||||||
|
}
|
||||||
return notification;
|
return notification;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
30
Oqtane.Server/Controllers/OutputCacheController.cs
Normal file
30
Oqtane.Server/Controllers/OutputCacheController.cs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
using Oqtane.Models;
|
||||||
|
using Oqtane.Services;
|
||||||
|
using Oqtane.Shared;
|
||||||
|
|
||||||
|
namespace Oqtane.Controllers
|
||||||
|
{
|
||||||
|
[Route(ControllerRoutes.ApiRoute)]
|
||||||
|
public class OutputCacheController : Controller
|
||||||
|
{
|
||||||
|
private readonly IOutputCacheService _cacheService;
|
||||||
|
|
||||||
|
public OutputCacheController(IOutputCacheService cacheService)
|
||||||
|
{
|
||||||
|
_cacheService = cacheService;
|
||||||
|
}
|
||||||
|
|
||||||
|
// DELETE api/<controller>/{tag}
|
||||||
|
[HttpDelete("{tag}")]
|
||||||
|
[Authorize(Roles = RoleNames.Admin)]
|
||||||
|
public async Task EvictByTag(string tag)
|
||||||
|
{
|
||||||
|
await _cacheService.EvictByTag(tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -12,6 +12,8 @@ using Oqtane.Infrastructure;
|
|||||||
using Oqtane.Enums;
|
using Oqtane.Enums;
|
||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using Oqtane.Managers;
|
||||||
|
using System.Net;
|
||||||
// ReSharper disable PartialTypeWithSinglePart
|
// ReSharper disable PartialTypeWithSinglePart
|
||||||
|
|
||||||
namespace Oqtane.Controllers
|
namespace Oqtane.Controllers
|
||||||
@ -20,13 +22,15 @@ namespace Oqtane.Controllers
|
|||||||
public class PackageController : Controller
|
public class PackageController : Controller
|
||||||
{
|
{
|
||||||
private readonly IInstallationManager _installationManager;
|
private readonly IInstallationManager _installationManager;
|
||||||
|
private readonly IUserManager _userManager;
|
||||||
private readonly IWebHostEnvironment _environment;
|
private readonly IWebHostEnvironment _environment;
|
||||||
private readonly IConfigManager _configManager;
|
private readonly IConfigManager _configManager;
|
||||||
private readonly ILogManager _logger;
|
private readonly ILogManager _logger;
|
||||||
|
|
||||||
public PackageController(IInstallationManager installationManager, IWebHostEnvironment environment, IConfigManager configManager, ILogManager logger)
|
public PackageController(IInstallationManager installationManager, IUserManager userManager, IWebHostEnvironment environment, IConfigManager configManager, ILogManager logger)
|
||||||
{
|
{
|
||||||
_installationManager = installationManager;
|
_installationManager = installationManager;
|
||||||
|
_userManager = userManager;
|
||||||
_environment = environment;
|
_environment = environment;
|
||||||
_configManager = configManager;
|
_configManager = configManager;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
@ -45,7 +49,7 @@ namespace Oqtane.Controllers
|
|||||||
{
|
{
|
||||||
client.DefaultRequestHeaders.Add("Referer", HttpContext.Request.Scheme + "://" + HttpContext.Request.Host.Value);
|
client.DefaultRequestHeaders.Add("Referer", HttpContext.Request.Scheme + "://" + HttpContext.Request.Host.Value);
|
||||||
client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(Constants.PackageId, Constants.Version));
|
client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(Constants.PackageId, Constants.Version));
|
||||||
packages = await GetJson<List<Package>>(client, url + $"/api/registry/packages/?id={_configManager.GetInstallationId()}&type={type.ToLower()}&version={Constants.Version}&search={search}&price={price}&package={package}&sort={sort}");
|
packages = await GetJson<List<Package>>(client, url + $"/api/registry/packages/?id={_configManager.GetInstallationId()}&type={type.ToLower()}&version={Constants.Version}&search={WebUtility.UrlEncode(search)}&price={price}&package={package}&sort={sort}&email={WebUtility.UrlEncode(GetPackageRegistryEmail())}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return packages;
|
return packages;
|
||||||
@ -64,7 +68,7 @@ namespace Oqtane.Controllers
|
|||||||
{
|
{
|
||||||
client.DefaultRequestHeaders.Add("Referer", HttpContext.Request.Scheme + "://" + HttpContext.Request.Host.Value);
|
client.DefaultRequestHeaders.Add("Referer", HttpContext.Request.Scheme + "://" + HttpContext.Request.Host.Value);
|
||||||
client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(Constants.PackageId, Constants.Version));
|
client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(Constants.PackageId, Constants.Version));
|
||||||
packages = await GetJson<List<Package>>(client, url + $"/api/registry/updates/?id={_configManager.GetInstallationId()}&version={Constants.Version}&type={type}");
|
packages = await GetJson<List<Package>>(client, url + $"/api/registry/updates/?id={_configManager.GetInstallationId()}&version={Constants.Version}&type={type}&email={WebUtility.UrlEncode(GetPackageRegistryEmail())}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return packages;
|
return packages;
|
||||||
@ -83,7 +87,7 @@ namespace Oqtane.Controllers
|
|||||||
{
|
{
|
||||||
client.DefaultRequestHeaders.Add("Referer", HttpContext.Request.Scheme + "://" + HttpContext.Request.Host.Value);
|
client.DefaultRequestHeaders.Add("Referer", HttpContext.Request.Scheme + "://" + HttpContext.Request.Host.Value);
|
||||||
client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(Constants.PackageId, Constants.Version));
|
client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue(Constants.PackageId, Constants.Version));
|
||||||
package = await GetJson<Package>(client, url + $"/api/registry/package/?id={_configManager.GetInstallationId()}&package={packageid}&version={version}&download={download}");
|
package = await GetJson<Package>(client, url + $"/api/registry/package/?id={_configManager.GetInstallationId()}&package={packageid}&version={version}&download={download}&email={WebUtility.UrlEncode(GetPackageRegistryEmail())}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (package != null)
|
if (package != null)
|
||||||
@ -117,6 +121,24 @@ namespace Oqtane.Controllers
|
|||||||
return package;
|
return package;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string GetPackageRegistryEmail()
|
||||||
|
{
|
||||||
|
var email = _configManager.GetSetting("PackageRegistryEmail", "");
|
||||||
|
if (string.IsNullOrEmpty(email))
|
||||||
|
{
|
||||||
|
if (User.Identity.IsAuthenticated)
|
||||||
|
{
|
||||||
|
var user = _userManager.GetUser(User.Identity.Name, -1);
|
||||||
|
if (user != null)
|
||||||
|
{
|
||||||
|
email = user.Email;
|
||||||
|
_configManager.AddOrUpdateSetting("PackageRegistryEmail", email, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return email;
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<T> GetJson<T>(HttpClient httpClient, string url)
|
private async Task<T> GetJson<T>(HttpClient httpClient, string url)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
@ -9,7 +9,8 @@ using System.Net;
|
|||||||
using Oqtane.Enums;
|
using Oqtane.Enums;
|
||||||
using Oqtane.Infrastructure;
|
using Oqtane.Infrastructure;
|
||||||
using Oqtane.Repository;
|
using Oqtane.Repository;
|
||||||
using System;
|
using System.Xml.Linq;
|
||||||
|
using Microsoft.AspNetCore.Diagnostics;
|
||||||
|
|
||||||
namespace Oqtane.Controllers
|
namespace Oqtane.Controllers
|
||||||
{
|
{
|
||||||
@ -189,15 +190,16 @@ namespace Oqtane.Controllers
|
|||||||
User user = _userPermissions.GetUser(User);
|
User user = _userPermissions.GetUser(User);
|
||||||
if (parent != null && parent.SiteId == _alias.SiteId && parent.IsPersonalizable && user.UserId == int.Parse(userid))
|
if (parent != null && parent.SiteId == _alias.SiteId && parent.IsPersonalizable && user.UserId == int.Parse(userid))
|
||||||
{
|
{
|
||||||
page = _pages.GetPage(parent.Path + "/" + user.Username, parent.SiteId);
|
var path = Utilities.GetFriendlyUrl(user.Username);
|
||||||
|
page = _pages.GetPage(parent.Path + "/" + path, parent.SiteId);
|
||||||
if (page == null)
|
if (page == null)
|
||||||
{
|
{
|
||||||
page = new Page();
|
page = new Page();
|
||||||
page.SiteId = parent.SiteId;
|
page.SiteId = parent.SiteId;
|
||||||
page.ParentId = parent.PageId;
|
page.ParentId = parent.PageId;
|
||||||
page.Name = (!string.IsNullOrEmpty(user.DisplayName)) ? user.DisplayName : user.Username;
|
page.Name = user.Username;
|
||||||
page.Path = parent.Path + "/" + user.Username;
|
page.Path = parent.Path + "/" + path;
|
||||||
page.Title = page.Name + " - " + parent.Name;
|
page.Title = ((!string.IsNullOrEmpty(user.DisplayName)) ? user.DisplayName : user.Username) + " - " + parent.Name;
|
||||||
page.Order = 0;
|
page.Order = 0;
|
||||||
page.IsNavigation = false;
|
page.IsNavigation = false;
|
||||||
page.Url = "";
|
page.Url = "";
|
||||||
@ -250,6 +252,11 @@ namespace Oqtane.Controllers
|
|||||||
|
|
||||||
_syncManager.AddSyncEvent(_alias, EntityNames.Page, page.PageId, SyncEventActions.Create);
|
_syncManager.AddSyncEvent(_alias, EntityNames.Page, page.PageId, SyncEventActions.Create);
|
||||||
_syncManager.AddSyncEvent(_alias, EntityNames.Site, page.SiteId, SyncEventActions.Refresh);
|
_syncManager.AddSyncEvent(_alias, EntityNames.Site, page.SiteId, SyncEventActions.Refresh);
|
||||||
|
|
||||||
|
// set user personalized page path
|
||||||
|
var setting = new Setting { EntityName = EntityNames.User, EntityId = page.UserId.Value, SettingName = $"PersonalizedPagePath:{page.SiteId}:{parent.PageId}", SettingValue = path, IsPrivate = false };
|
||||||
|
_settings.AddSetting(setting);
|
||||||
|
_syncManager.AddSyncEvent(_alias, EntityNames.User, user.UserId, SyncEventActions.Update);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -274,18 +281,14 @@ namespace Oqtane.Controllers
|
|||||||
// get current page permissions
|
// get current page permissions
|
||||||
var currentPermissions = _permissionRepository.GetPermissions(page.SiteId, EntityNames.Page, page.PageId).ToList();
|
var currentPermissions = _permissionRepository.GetPermissions(page.SiteId, EntityNames.Page, page.PageId).ToList();
|
||||||
|
|
||||||
page = _pages.UpdatePage(page);
|
// preserve new path and deleted status
|
||||||
|
var newPath = page.Path;
|
||||||
|
var deleted = page.IsDeleted;
|
||||||
|
page.Path = currentPage.Path;
|
||||||
|
page.IsDeleted = currentPage.IsDeleted;
|
||||||
|
|
||||||
// save url mapping if page path changed
|
// update page
|
||||||
if (currentPage.Path != page.Path)
|
UpdatePage(page, page.PageId, page.Path, newPath, deleted);
|
||||||
{
|
|
||||||
var urlMapping = _urlMappings.GetUrlMapping(page.SiteId, currentPage.Path);
|
|
||||||
if (urlMapping != null)
|
|
||||||
{
|
|
||||||
urlMapping.MappedUrl = page.Path;
|
|
||||||
_urlMappings.UpdateUrlMapping(urlMapping);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// get differences between current and new page permissions
|
// get differences between current and new page permissions
|
||||||
var added = GetPermissionsDifferences(page.PermissionList, currentPermissions);
|
var added = GetPermissionsDifferences(page.PermissionList, currentPermissions);
|
||||||
@ -315,6 +318,7 @@ namespace Oqtane.Controllers
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// permissions removed
|
// permissions removed
|
||||||
foreach (Permission permission in removed)
|
foreach (Permission permission in removed)
|
||||||
{
|
{
|
||||||
@ -338,8 +342,29 @@ namespace Oqtane.Controllers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_syncManager.AddSyncEvent(_alias, EntityNames.Page, page.PageId, SyncEventActions.Update);
|
|
||||||
_syncManager.AddSyncEvent(_alias, EntityNames.Site, page.SiteId, SyncEventActions.Refresh);
|
_syncManager.AddSyncEvent(_alias, EntityNames.Site, page.SiteId, SyncEventActions.Refresh);
|
||||||
|
|
||||||
|
// personalized page
|
||||||
|
if (page.UserId != null && currentPage.Path != page.Path)
|
||||||
|
{
|
||||||
|
// set user personalized page path
|
||||||
|
var settingName = $"PersonalizedPagePath:{page.SiteId}:{page.ParentId}";
|
||||||
|
var path = page.Path.Substring(page.Path.LastIndexOf("/") + 1);
|
||||||
|
var settings = _settings.GetSettings(EntityNames.User, page.UserId.Value).ToList();
|
||||||
|
var setting = settings.FirstOrDefault(item => item.SettingName == settingName);
|
||||||
|
if (setting == null)
|
||||||
|
{
|
||||||
|
setting = new Setting { EntityName = EntityNames.User, EntityId = page.UserId.Value, SettingName = settingName, SettingValue = path, IsPrivate = false };
|
||||||
|
_settings.AddSetting(setting);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setting.SettingValue = path;
|
||||||
|
_settings.UpdateSetting(setting);
|
||||||
|
}
|
||||||
|
_syncManager.AddSyncEvent(_alias, EntityNames.User, page.UserId.Value, SyncEventActions.Update);
|
||||||
|
}
|
||||||
|
|
||||||
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Page Updated {Page}", page);
|
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Page Updated {Page}", page);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -351,6 +376,39 @@ namespace Oqtane.Controllers
|
|||||||
return page;
|
return page;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void UpdatePage(Page page, int pageId, string oldPath, string newPath, bool deleted)
|
||||||
|
{
|
||||||
|
var update = (page.PageId == pageId);
|
||||||
|
if (oldPath != newPath)
|
||||||
|
{
|
||||||
|
var urlMapping = _urlMappings.GetUrlMapping(page.SiteId, page.Path);
|
||||||
|
if (urlMapping != null)
|
||||||
|
{
|
||||||
|
urlMapping.MappedUrl = newPath + page.Path.Substring(oldPath.Length);
|
||||||
|
_urlMappings.UpdateUrlMapping(urlMapping);
|
||||||
|
}
|
||||||
|
|
||||||
|
page.Path = newPath + page.Path.Substring(oldPath.Length);
|
||||||
|
update = true;
|
||||||
|
}
|
||||||
|
if (deleted != page.IsDeleted)
|
||||||
|
{
|
||||||
|
page.IsDeleted = deleted;
|
||||||
|
update = true;
|
||||||
|
}
|
||||||
|
if (update)
|
||||||
|
{
|
||||||
|
_pages.UpdatePage(page);
|
||||||
|
_syncManager.AddSyncEvent(_alias, EntityNames.Page, page.PageId, SyncEventActions.Update);
|
||||||
|
}
|
||||||
|
|
||||||
|
// update any children
|
||||||
|
foreach (var _page in _pages.GetPages(page.SiteId).Where(item => item.ParentId == page.PageId))
|
||||||
|
{
|
||||||
|
UpdatePage(_page, pageId, oldPath, newPath, deleted);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private List<Permission> GetPermissionsDifferences(List<Permission> permissions1, List<Permission> permissions2)
|
private List<Permission> GetPermissionsDifferences(List<Permission> permissions1, List<Permission> permissions2)
|
||||||
{
|
{
|
||||||
var differences = new List<Permission>();
|
var differences = new List<Permission>();
|
||||||
|
@ -64,7 +64,7 @@ namespace Oqtane.Controllers
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// suppress unauthorized visitor logging as it is usually caused by clients that do not support cookies
|
// suppress unauthorized visitor logging as it is usually caused by clients that do not support cookies or private browsing sessions
|
||||||
if (entityName != EntityNames.Visitor)
|
if (entityName != EntityNames.Visitor)
|
||||||
{
|
{
|
||||||
_logger.Log(LogLevel.Error, this, LogFunction.Read, "User Not Authorized To Access Settings {EntityName} {EntityId}", entityName, entityId);
|
_logger.Log(LogLevel.Error, this, LogFunction.Read, "User Not Authorized To Access Settings {EntityName} {EntityId}", entityName, entityId);
|
||||||
|
@ -53,7 +53,9 @@ namespace Oqtane.Controllers
|
|||||||
systeminfo.Add("Logging:LogLevel:Default", _configManager.GetSetting("Logging:LogLevel:Default", "Information"));
|
systeminfo.Add("Logging:LogLevel:Default", _configManager.GetSetting("Logging:LogLevel:Default", "Information"));
|
||||||
systeminfo.Add("Logging:LogLevel:Notify", _configManager.GetSetting("Logging:LogLevel:Notify", "Error"));
|
systeminfo.Add("Logging:LogLevel:Notify", _configManager.GetSetting("Logging:LogLevel:Notify", "Error"));
|
||||||
systeminfo.Add("UseSwagger", _configManager.GetSetting("UseSwagger", "true"));
|
systeminfo.Add("UseSwagger", _configManager.GetSetting("UseSwagger", "true"));
|
||||||
|
systeminfo.Add("CacheControl", _configManager.GetSetting("CacheControl", ""));
|
||||||
systeminfo.Add("PackageRegistryUrl", _configManager.GetSetting("PackageRegistryUrl", Constants.PackageRegistryUrl));
|
systeminfo.Add("PackageRegistryUrl", _configManager.GetSetting("PackageRegistryUrl", Constants.PackageRegistryUrl));
|
||||||
|
systeminfo.Add("PackageRegistryEmail", _configManager.GetSetting("PackageRegistryEmail", ""));
|
||||||
break;
|
break;
|
||||||
case "log":
|
case "log":
|
||||||
string log = "";
|
string log = "";
|
||||||
|
@ -280,7 +280,7 @@ namespace Oqtane.Controllers
|
|||||||
{
|
{
|
||||||
{ "FrameworkVersion", theme.Version },
|
{ "FrameworkVersion", theme.Version },
|
||||||
{ "ClientReference", $"<PackageReference Include=\"Oqtane.Client\" Version=\"{theme.Version}\" />" },
|
{ "ClientReference", $"<PackageReference Include=\"Oqtane.Client\" Version=\"{theme.Version}\" />" },
|
||||||
{ "SharedReference", $"<PackageReference Include=\"Oqtane.Client\" Version=\"{theme.Version}\" />" },
|
{ "SharedReference", $"<PackageReference Include=\"Oqtane.Shared\" Version=\"{theme.Version}\" />" },
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -217,7 +217,7 @@ namespace Oqtane.Controllers
|
|||||||
|
|
||||||
// DELETE api/<controller>/5?siteid=x
|
// DELETE api/<controller>/5?siteid=x
|
||||||
[HttpDelete("{id}")]
|
[HttpDelete("{id}")]
|
||||||
[Authorize(Policy = $"{EntityNames.User}:{PermissionNames.Write}:{RoleNames.Admin}")]
|
[Authorize(Policy = $"{EntityNames.User}:{PermissionNames.Write}:{RoleNames.Host}")]
|
||||||
public async Task Delete(int id, string siteid)
|
public async Task Delete(int id, string siteid)
|
||||||
{
|
{
|
||||||
User user = _users.GetUser(id, false);
|
User user = _users.GetUser(id, false);
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user