Compare commits
452 Commits
v10.0.0
...
9833d37ebe
| Author | SHA1 | Date | |
|---|---|---|---|
| 9833d37ebe | |||
|
|
6299412fa5 | ||
|
|
7c4c6f9c02 | ||
|
|
2dfa7b6b2f | ||
|
|
0ea27e7147 | ||
|
|
6852b28b71 | ||
|
|
1741029057 | ||
|
|
a47b8942eb | ||
|
|
a110497967 | ||
|
|
59b2a34444 | ||
|
|
6814a7bcdb | ||
|
|
4f7f24a725 | ||
|
|
99949bdeb9 | ||
|
|
26eefa336f | ||
|
|
4c39aadff4 | ||
|
|
3364f18341 | ||
|
|
7fd4c617db | ||
|
|
a801048ad5 | ||
|
|
d114ae488c | ||
|
|
2d6b05650b | ||
|
|
8ed48c6702 | ||
|
|
6f7789ab3b | ||
|
|
72a684a35a | ||
|
|
f8ca688b2d | ||
|
|
edb3f3750c | ||
|
|
96291c4a0e | ||
|
|
e9b756092e | ||
|
|
8349f73e1e | ||
|
|
7d810ead6c | ||
|
|
d01622409a | ||
|
|
c01fa810f2 | ||
|
|
64e51038a6 | ||
|
|
4e07b41772 | ||
|
|
50b2b80778 | ||
|
|
f6d9300cd0 | ||
|
|
ebcc3866ab | ||
|
|
36ab6f40c7 | ||
|
|
12809df732 | ||
|
|
82761530ac | ||
|
|
3eb1542b96 | ||
|
|
b27b38eccf | ||
|
|
e3b0bdd500 | ||
|
|
957309b5f0 | ||
|
|
c7d32dc5f0 | ||
|
|
aaffb7b84d | ||
|
|
0648b23c75 | ||
|
|
f09b21295a | ||
|
|
f57d296fcb | ||
|
|
d2e5ab61da | ||
|
|
d7d3273018 | ||
|
|
c2bb6be2da | ||
|
|
976d8a3369 | ||
|
|
ae9beedca2 | ||
|
|
0d668f1469 | ||
|
|
8de67fdaec | ||
|
|
a4c94c0dda | ||
|
|
91b15b5e2a | ||
|
|
489629e642 | ||
|
|
ef92a88908 | ||
|
|
f249801541 | ||
|
|
147ee8b1e7 | ||
|
|
263091c27e | ||
|
|
aac1bb582b | ||
|
|
b097871d97 | ||
|
|
72542f0146 | ||
|
|
6a4f3bdb8e | ||
|
|
e65f61bd65 | ||
|
|
2c4dfe0ee0 | ||
|
|
6160941aee | ||
|
|
3db3d9561b | ||
|
|
cf61a58b4c | ||
|
|
e988a9f9c9 | ||
|
|
d1b40f0603 | ||
|
|
772f6ad490 | ||
|
|
f1934028a1 | ||
|
|
5cb0598025 | ||
|
|
18730c5e53 | ||
|
|
f79c3ae994 | ||
|
|
779a3dd379 | ||
|
|
c3a0a96623 | ||
|
|
f9d6ad2c0f | ||
|
|
1cb0a45715 | ||
|
|
6464604f5c | ||
|
|
573a914699 | ||
|
|
ebbe618e98 | ||
|
|
0cc1b5a3e9 | ||
|
|
50ccd29872 | ||
|
|
00d14552b1 | ||
|
|
cad5694145 | ||
|
|
06e555ddd1 | ||
|
|
46aa5225c6 | ||
|
|
09f6a1d531 | ||
|
|
f8633fd390 | ||
|
|
9aad400038 | ||
|
|
91ce840592 | ||
|
|
458c8534c7 | ||
|
|
f7df3a4720 | ||
|
|
36789495df | ||
|
|
ed0b341f76 | ||
|
|
0d4d51448e | ||
|
|
3df2c04795 | ||
|
|
12f06a7662 | ||
|
|
f0cab1ca79 | ||
|
|
ae0c4c1099 | ||
|
|
dc4ded82b1 | ||
|
|
8752b24723 | ||
|
|
289252d39c | ||
|
|
2736fa451c | ||
|
|
31954a971c | ||
|
|
a6006ce1fe | ||
|
|
3db09a2fa6 | ||
|
|
5c2bd8093a | ||
|
|
8b8048724a | ||
|
|
060eaa7aff | ||
|
|
67dbc4b7ca | ||
|
|
0fd97d34d9 | ||
|
|
ba51342fd6 | ||
|
|
13a58ed099 | ||
|
|
48a70c8be3 | ||
|
|
4db58c2866 | ||
|
|
539bad1463 | ||
|
|
df7f3f7bba | ||
|
|
064448deaf | ||
|
|
b25279cdcf | ||
|
|
26c8d00cca | ||
|
|
912ed66547 | ||
|
|
6f6870b16d | ||
|
|
1ddf6fe74e | ||
|
|
3e0b5bfa09 | ||
|
|
f60df5c8dc | ||
|
|
3af03d308e | ||
|
|
7605fd7ec7 | ||
|
|
e85b1001c6 | ||
|
|
87620f2251 | ||
|
|
772da734dc | ||
|
|
646b6fea84 | ||
|
|
dd816f7c44 | ||
|
|
c601c0cdc4 | ||
|
|
a4f7d1f745 | ||
|
|
a4980eac33 | ||
|
|
d3c4e78baa | ||
|
|
5bf5d2d515 | ||
|
|
657c6620d5 | ||
|
|
de5725b92c | ||
|
|
d9db41ac99 | ||
|
|
dcd862900f | ||
|
|
0f8d22e6f0 | ||
|
|
0d75e0f555 | ||
|
|
84e8edb159 | ||
|
|
edb6109db9 | ||
|
|
4cc9348769 | ||
|
|
1a6420ee45 | ||
|
|
486132f918 | ||
|
|
a18f317e85 | ||
|
|
171735fac4 | ||
|
|
72d4361fb2 | ||
|
|
69fc1c9895 | ||
|
|
831bc31e06 | ||
|
|
68445c40f7 | ||
|
|
2b2d960c3e | ||
|
|
d687d81d0c | ||
|
|
2d19f21b2e | ||
|
|
e85a2a6cf5 | ||
|
|
6d35d40829 | ||
|
|
4b3377cc78 | ||
|
|
971ba796ef | ||
|
|
3195e4b46f | ||
|
|
10c1779f84 | ||
|
|
b5c9717f71 | ||
|
|
d13e6fcdad | ||
|
|
357337572e | ||
|
|
e95a6b774e | ||
|
|
59bdc4c04e | ||
|
|
ad300a58c1 | ||
|
|
8f25d9f06a | ||
|
|
3dcb391a14 | ||
|
|
9073b8a9ff | ||
|
|
6f2e676c00 | ||
|
|
dbd9388d4c | ||
|
|
ddd6dfc475 | ||
|
|
16e2efdd66 | ||
|
|
c0e191537f | ||
|
|
1652afc10f | ||
|
|
aab0dd96dd | ||
|
|
783d01bf9f | ||
|
|
60b2e50511 | ||
|
|
6520f8ade4 | ||
|
|
57deeb6acf | ||
|
|
49102491a6 | ||
|
|
dff2261994 | ||
|
|
daca055f90 | ||
|
|
b2ef3cc574 | ||
|
|
8bd035b8f4 | ||
|
|
8817af42bd | ||
|
|
7b1a12f1e7 | ||
|
|
d313c2f3a5 | ||
|
|
831f7c5738 | ||
|
|
7bb84fbe33 | ||
|
|
0dc9382215 | ||
|
|
563696c7a5 | ||
|
|
66963a6508 | ||
|
|
8ec4922cd3 | ||
|
|
ed7d1eef14 | ||
|
|
7ab1184a04 | ||
|
|
d638f9492d | ||
|
|
6d4870f41a | ||
|
|
81f4f87493 | ||
|
|
4d1ba921dc | ||
|
|
d6458eeaf6 | ||
|
|
f4f935ab43 | ||
|
|
738ad9bbfa | ||
|
|
a470db594e | ||
|
|
ce905499b0 | ||
|
|
7e6b60405b | ||
|
|
8a4275c240 | ||
|
|
5735f65628 | ||
|
|
8749ab7588 | ||
|
|
912c01cdf8 | ||
|
|
d6bbc6b82f | ||
|
|
1588d4626b | ||
|
|
c95725d444 | ||
|
|
d3e4c57ede | ||
|
|
e34013ace5 | ||
|
|
3be2b9c720 | ||
|
|
256af74e0c | ||
|
|
0295b66c22 | ||
|
|
3393ece21d | ||
|
|
1ac722bd06 | ||
|
|
d0fb8dc5fc | ||
|
|
6006e6f63c | ||
|
|
0a3f60c007 | ||
|
|
472e8eadec | ||
|
|
d2c515d837 | ||
|
|
0446bdb970 | ||
|
|
adf2468373 | ||
|
|
54d3f1c659 | ||
|
|
e3b45a2329 | ||
|
|
1a777b29e0 | ||
|
|
0c3754ca86 | ||
|
|
b0211b2e6e | ||
|
|
20548710ce | ||
|
|
92f4a8b683 | ||
|
|
c1709db676 | ||
|
|
7d7ecf4757 | ||
|
|
f1c85d23d6 | ||
|
|
780e2a8484 | ||
|
|
0624d539bf | ||
|
|
e98d84784a | ||
|
|
1678817bf0 | ||
|
|
bf0fb0cbae | ||
|
|
9dc91aec58 | ||
|
|
8ba480f168 | ||
|
|
9d42dac7d9 | ||
|
|
236aa84f87 | ||
|
|
a6f7ec9bd5 | ||
|
|
2bf8c947b6 | ||
|
|
c8872a5e99 | ||
|
|
36f04708ba | ||
|
|
3b990aa633 | ||
|
|
3192ab8452 | ||
|
|
51638fdcb0 | ||
|
|
95f98b89fe | ||
|
|
6c484ea9cd | ||
|
|
ea0271ba79 | ||
|
|
526b797d03 | ||
|
|
0d0efaf4ca | ||
| f3175b6b06 | |||
| 90e254fae6 | |||
| 4a7d088612 | |||
|
|
5c70f4f6a7 | ||
|
|
b0d624034a | ||
|
|
43ee682d1f | ||
|
|
d4399955ac | ||
|
|
86f88c4f7c | ||
|
|
d6ea610764 | ||
|
|
ff8417ed31 | ||
|
|
bf49e52cbb | ||
|
|
2844b793fa | ||
|
|
9218863dae | ||
|
|
ef20d870ee | ||
|
|
76c79206b6 | ||
|
|
b4c6b6b794 | ||
|
|
6db5c924c7 | ||
|
|
51aecacee6 | ||
|
|
b3b8febd12 | ||
|
|
7c770d9a9d | ||
|
|
30b89fe56f | ||
|
|
06870f2577 | ||
|
|
e84170b8ea | ||
|
|
e8d26b2cb2 | ||
|
|
a8f87ea572 | ||
|
|
1fb78a457f | ||
|
|
82cb7a8c02 | ||
|
|
cae61ab701 | ||
|
|
975c1955f2 | ||
|
|
fc2a8cb9dd | ||
|
|
e2d9aaaa0f | ||
|
|
aca70dd6c7 | ||
|
|
5a1b3a7017 | ||
|
|
6733299290 | ||
|
|
e11e021750 | ||
|
|
0ad5bd2335 | ||
|
|
255764a4ac | ||
|
|
7330d5b2a7 | ||
|
|
5d3e507672 | ||
|
|
15ee2c9bcb | ||
|
|
e1163b7ab1 | ||
|
|
96bea78424 | ||
|
|
8ba67a63ba | ||
|
|
8120db84f4 | ||
|
|
4e6a6afaab | ||
|
|
417a6bf226 | ||
|
|
5e5d91bd93 | ||
|
|
5c536aafc2 | ||
|
|
15efe75aa8 | ||
|
|
a10575bfc3 | ||
|
|
57e26d6156 | ||
|
|
1682a123b4 | ||
|
|
13064cdb26 | ||
|
|
f74eda274a | ||
|
|
7dee55ce30 | ||
|
|
8113c82da8 | ||
|
|
2d684d23d7 | ||
|
|
33da5809e3 | ||
|
|
68a6e6862e | ||
|
|
07fb15d5be | ||
|
|
0f791253ba | ||
|
|
828d070194 | ||
|
|
0d5bb3f3b3 | ||
|
|
32de1ca511 | ||
|
|
f5f00c51c1 | ||
|
|
1305125390 | ||
|
|
c539f41ebf | ||
|
|
075e754830 | ||
|
|
87fd9dd000 | ||
|
|
e34321f727 | ||
|
|
a48dff4a85 | ||
|
|
576d3d0b56 | ||
|
|
640c2cee00 | ||
|
|
1958787185 | ||
|
|
073e1ac13a | ||
|
|
c0eacd0d6b | ||
|
|
7938eaf123 | ||
|
|
b4f8896713 | ||
|
|
c418ddf240 | ||
|
|
6c6b36f3da | ||
|
|
c0c71251ab | ||
|
|
2685c18798 | ||
|
|
7f914271ed | ||
|
|
7b37cc3c82 | ||
|
|
ec2afd5f03 | ||
|
|
e62268af2e | ||
|
|
01ad99b925 | ||
|
|
a33e9d25cc | ||
|
|
a0e45cbea0 | ||
|
|
171314947c | ||
|
|
6b883b3f94 | ||
|
|
c99348650f | ||
|
|
011375a081 | ||
|
|
38f43c9988 | ||
|
|
f459d0503a | ||
|
|
c2912a291e | ||
|
|
53a88e0c9f | ||
|
|
9cf670bcad | ||
|
|
156e7bd3d4 | ||
|
|
009829c8f9 | ||
|
|
d7c0b0aaaf | ||
|
|
06071fb7f9 | ||
|
|
a51f87d743 | ||
|
|
12fa6ff4f0 | ||
|
|
23d14c62a5 | ||
|
|
ad993c6180 | ||
|
|
e99ce3ac7b | ||
|
|
270b447fbd | ||
|
|
1c55a74ff1 | ||
|
|
47f42747cb | ||
|
|
86a3f67871 | ||
|
|
29b87f809f | ||
|
|
8bd63fdc61 | ||
|
|
cf88347c3d | ||
|
|
171f9c84a0 | ||
|
|
a6069e572d | ||
|
|
16a13a6c01 | ||
|
|
ac31cd3f41 | ||
|
|
321fe2954e | ||
|
|
e2f174e0b5 | ||
|
|
50c085fe65 | ||
|
|
4e63c9ce9d | ||
|
|
fb6e8bb233 | ||
|
|
44103c1311 | ||
|
|
6ef6e6aac8 | ||
|
|
9499012825 | ||
|
|
6af73873d7 | ||
|
|
0a04035b2f | ||
|
|
1e3c176ddf | ||
|
|
f5bb9a934c | ||
|
|
476cf7c080 | ||
|
|
1279c30fbb | ||
|
|
012b7ba6ed | ||
|
|
e08c033e76 | ||
|
|
dafbae7237 | ||
|
|
31b8080a3d | ||
|
|
28c7617227 | ||
|
|
7f7c53dabe | ||
|
|
950a9bf2fa | ||
|
|
f0d4a416be | ||
|
|
708d79ffaf | ||
|
|
583bd3b511 | ||
|
|
528cbde7e5 | ||
|
|
11284f0285 | ||
|
|
dc9d4a1938 | ||
|
|
e58ee4e5b1 | ||
|
|
4339833aa3 | ||
|
|
a06b1becc5 | ||
| 29ac9334ba | |||
|
|
a59f2e7ca6 | ||
|
|
b1cc0ffc13 | ||
|
|
4e49092434 | ||
|
|
026d716ece | ||
| 3f90653894 | |||
| ea056165ca | |||
|
|
a85ae69ed1 | ||
|
|
33f525dbda | ||
|
|
4ba7e034b7 | ||
|
|
74a5fb656e | ||
|
|
bc5ce74925 | ||
|
|
338c652635 | ||
|
|
4834761f64 | ||
|
|
d0ef5d0fe3 | ||
|
|
7d9b102ec4 | ||
|
|
5b2dff254f | ||
|
|
986c9d9f72 | ||
|
|
4db37059cd | ||
|
|
378c68be4b | ||
|
|
ebca580a0b | ||
|
|
413df647d3 | ||
|
|
a1011ed709 | ||
|
|
71be6d4ded | ||
|
|
f586401a14 | ||
| 7a9941fe66 | |||
| b3b39f583a | |||
| 92c554e854 | |||
|
|
fa384cb6f3 | ||
|
|
05db1bcbfb | ||
|
|
bdd6d9781c | ||
|
|
d23a5ad91b | ||
|
|
a50e179744 | ||
|
|
874d9f32a9 | ||
| 391827222e | |||
| c1721bd1a1 | |||
| f6630ae241 | |||
| 424cab64a8 |
23
.gitea/workflows/build-oqtane.yml
Normal file
23
.gitea/workflows/build-oqtane.yml
Normal file
@@ -0,0 +1,23 @@
|
||||
name: build-oqtane
|
||||
on:
|
||||
- push
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build the oqtane
|
||||
runs-on: mcr.microsoft.com/dotnet/sdk:10.0-noble-amd64
|
||||
steps:
|
||||
- name: "Git clone"
|
||||
run: git clone ${{ gitea.server_url }}/${{ gitea.repository }}.git .
|
||||
- name: "Git checkout"
|
||||
run: git checkout "${{ gitea.sha }}"
|
||||
- name: "Oqtane Framework bauen"
|
||||
run: dotnet build -c Release ./oqtane.framework/Oqtane.slnx
|
||||
- name: "Oqtane Framework publish"
|
||||
run: dotnet publish -c Release ./oqtane.framework/Oqtane.slnx -o ./out
|
||||
- name: Upload Package
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
include-hidden-files: true
|
||||
name: oqtane.framework-amd64
|
||||
path: ./out
|
||||
@@ -2,7 +2,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Configurations>Debug;Release</Configurations>
|
||||
<Version>10.0.0</Version>
|
||||
<Version>10.1.2</Version>
|
||||
<Product>Oqtane</Product>
|
||||
<Authors>Shaun Walker</Authors>
|
||||
<Company>.NET Foundation</Company>
|
||||
@@ -10,7 +10,7 @@
|
||||
<Copyright>.NET Foundation</Copyright>
|
||||
<PackageProjectUrl>https://www.oqtane.org</PackageProjectUrl>
|
||||
<PackageLicenseUrl>https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE</PackageLicenseUrl>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.0</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v10.1.2</PackageReleaseNotes>
|
||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
</PropertyGroup>
|
||||
|
||||
27
Dockerfile
Normal file
27
Dockerfile
Normal file
@@ -0,0 +1,27 @@
|
||||
# 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 /codefiles
|
||||
|
||||
COPY --from=publish /source/publish/ /codefiles/
|
||||
|
||||
COPY entrypoint.sh .
|
||||
RUN chmod +x entrypoint.sh
|
||||
|
||||
ENTRYPOINT ["./entrypoint.sh"]
|
||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018-2025 .NET Foundation
|
||||
Copyright (c) 2018-2026 .NET Foundation
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -12,10 +12,10 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="10.0.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="10.0.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.5" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -23,7 +23,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Oqtane.Client" Version="10.0.0" />
|
||||
<PackageReference Include="Oqtane.Client" Version="10.1.2" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
@using Microsoft.Extensions.Localization
|
||||
@using Microsoft.JSInterop
|
||||
@using Microsoft.AspNetCore.Authorization
|
||||
|
||||
@using Oqtane
|
||||
@using Oqtane.Models
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2011/08/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>Oqtane.Application.Template</id>
|
||||
<version>10.0.0</version>
|
||||
<version>10.1.2</version>
|
||||
<title>Oqtane Application Template For Blazor</title>
|
||||
<authors>Shaun Walker</authors>
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
|
||||
@@ -22,9 +22,9 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="10.0.5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="10.0.5" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.5" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -33,7 +33,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Oqtane.Server" Version="10.0.0" />
|
||||
<PackageReference Include="Oqtane.Server" Version="10.1.2" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
@namespace [Owner].Module.[Module]
|
||||
@inherits ModuleBase
|
||||
@inject I[Module]Service [Module]Service
|
||||
@inject I[Module]Service Client[Module]Service
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IStringLocalizer<Edit> Localizer
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
if (PageState.Action == "Edit")
|
||||
{
|
||||
_id = Int32.Parse(PageState.QueryString["id"]);
|
||||
[Module] [Module] = await [Module]Service.Get[Module]Async(_id, ModuleState.ModuleId);
|
||||
[Module] [Module] = await Client[Module]Service.Get[Module]Async(_id, ModuleState.ModuleId);
|
||||
if ([Module] != null)
|
||||
{
|
||||
_name = [Module].Name;
|
||||
@@ -81,14 +81,14 @@
|
||||
[Module] [Module] = new [Module]();
|
||||
[Module].ModuleId = ModuleState.ModuleId;
|
||||
[Module].Name = _name;
|
||||
[Module] = await [Module]Service.Add[Module]Async([Module]);
|
||||
[Module] = await Client[Module]Service.Add[Module]Async([Module]);
|
||||
await logger.LogInformation("[Module] Added {[Module]}", [Module]);
|
||||
}
|
||||
else
|
||||
{
|
||||
[Module] [Module] = await [Module]Service.Get[Module]Async(_id, ModuleState.ModuleId);
|
||||
[Module] [Module] = await Client[Module]Service.Get[Module]Async(_id, ModuleState.ModuleId);
|
||||
[Module].Name = _name;
|
||||
await [Module]Service.Update[Module]Async([Module]);
|
||||
await Client[Module]Service.Update[Module]Async([Module]);
|
||||
await logger.LogInformation("[Module] Updated {[Module]}", [Module]);
|
||||
}
|
||||
NavigationManager.NavigateTo(NavigateUrl());
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
@namespace [Owner].Module.[Module]
|
||||
@inherits ModuleBase
|
||||
@inject I[Module]Service [Module]Service
|
||||
@inject I[Module]Service Client[Module]Service
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IStringLocalizer<Index> Localizer
|
||||
|
||||
@@ -52,7 +52,7 @@ else
|
||||
{
|
||||
try
|
||||
{
|
||||
_[Module]s = await [Module]Service.Get[Module]sAsync(ModuleState.ModuleId);
|
||||
_[Module]s = await Client[Module]Service.Get[Module]sAsync(ModuleState.ModuleId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -65,9 +65,9 @@ else
|
||||
{
|
||||
try
|
||||
{
|
||||
await [Module]Service.Delete[Module]Async([Module].[Module]Id, ModuleState.ModuleId);
|
||||
await Client[Module]Service.Delete[Module]Async([Module].[Module]Id, ModuleState.ModuleId);
|
||||
await logger.LogInformation("[Module] Deleted {[Module]}", [Module]);
|
||||
_[Module]s = await [Module]Service.Get[Module]sAsync(ModuleState.ModuleId);
|
||||
_[Module]s = await Client[Module]Service.Get[Module]sAsync(ModuleState.ModuleId);
|
||||
StateHasChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
@namespace [Owner].Module.[Module]
|
||||
@inherits ModuleBase
|
||||
@implements Oqtane.Interfaces.ISettingsControl
|
||||
@inject ISettingService SettingService
|
||||
@inject IStringLocalizer<Settings> Localizer
|
||||
|
||||
|
||||
@@ -7,22 +7,10 @@ using Oqtane.Shared;
|
||||
|
||||
namespace [Owner].Module.[Module].Services
|
||||
{
|
||||
public interface I[Module]Service
|
||||
|
||||
public class Client[Module]Service : ServiceBase, I[Module]Service
|
||||
{
|
||||
Task<List<Models.[Module]>> Get[Module]sAsync(int ModuleId);
|
||||
|
||||
Task<Models.[Module]> Get[Module]Async(int [Module]Id, int ModuleId);
|
||||
|
||||
Task<Models.[Module]> Add[Module]Async(Models.[Module] [Module]);
|
||||
|
||||
Task<Models.[Module]> Update[Module]Async(Models.[Module] [Module]);
|
||||
|
||||
Task Delete[Module]Async(int [Module]Id, int ModuleId);
|
||||
}
|
||||
|
||||
public class [Module]Service : ServiceBase, I[Module]Service
|
||||
{
|
||||
public [Module]Service(HttpClient http, SiteState siteState) : base(http, siteState) { }
|
||||
public Client[Module]Service(HttpClient http, SiteState siteState) : base(http, siteState) { }
|
||||
|
||||
private string Apiurl => CreateApiUrl("[Module]");
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace [Owner].Module.[Module].Startup
|
||||
{
|
||||
if (!services.Any(s => s.ServiceType == typeof(I[Module]Service)))
|
||||
{
|
||||
services.AddScoped<I[Module]Service, [Module]Service>();
|
||||
services.AddScoped<I[Module]Service, Client[Module]Service>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace [Owner].Module.[Module].Services
|
||||
{
|
||||
public interface I[Module]Service
|
||||
{
|
||||
Task<List<Models.[Module]>> Get[Module]sAsync(int ModuleId);
|
||||
|
||||
Task<Models.[Module]> Get[Module]Async(int [Module]Id, int ModuleId);
|
||||
|
||||
Task<Models.[Module]> Add[Module]Async(Models.[Module] [Module]);
|
||||
|
||||
Task<Models.[Module]> Update[Module]Async(Models.[Module] [Module]);
|
||||
|
||||
Task Delete[Module]Async(int [Module]Id, int ModuleId);
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Oqtane.Shared" Version="10.0.0" />
|
||||
<PackageReference Include="Oqtane.Shared" Version="10.1.2" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -57,6 +57,9 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||
services.AddScoped<ICookieConsentService, CookieConsentService>();
|
||||
services.AddScoped<ITimeZoneService, TimeZoneService>();
|
||||
services.AddScoped<IMigrationHistoryService, MigrationHistoryService>();
|
||||
services.AddScoped<ISiteGroupService, SiteGroupService>();
|
||||
services.AddScoped<ISiteGroupMemberService, SiteGroupMemberService>();
|
||||
services.AddScoped<ISiteTaskService, SiteTaskService>();
|
||||
services.AddScoped<IOutputCacheService, OutputCacheService>();
|
||||
|
||||
// providers
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
{
|
||||
string url = NavigateUrl(p.Path);
|
||||
<div class="col-md-2 mx-auto text-center my-3">
|
||||
<NavLink class="nav-link text-body" href="@url" Match="NavLinkMatch.All">
|
||||
<NavLink class="nav-link text-body" href="@url" Match="NavLinkMatch.All" @attributes="_attributes">
|
||||
<h2><span class="@p.Icon" aria-hidden="true"></span></h2>
|
||||
<div class="lead">@((MarkupString)SharedLocalizer[p.Name].ToString().Replace(" ", "<br />"))</div>
|
||||
</NavLink>
|
||||
@@ -24,13 +24,19 @@
|
||||
}
|
||||
|
||||
@code {
|
||||
private List<Page> _pages;
|
||||
Dictionary<string, object> _attributes { get; set; } = new();
|
||||
private List<Page> _pages;
|
||||
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
|
||||
public override string RenderMode => RenderModes.Static;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
if (PageState.RenderMode == RenderModes.Static && !PageState.Site.EnhancedNavigation)
|
||||
{
|
||||
_attributes.Add("data-enhance-nav", "true"); // Admin Dashboard utilizes enhanced navigation
|
||||
}
|
||||
|
||||
var admin = PageState.Pages.FirstOrDefault(item => item.Path == "admin");
|
||||
if (admin != null)
|
||||
{
|
||||
|
||||
117
Oqtane.Client/Modules/Admin/GlobalReplace/Index.razor
Normal file
117
Oqtane.Client/Modules/Admin/GlobalReplace/Index.razor
Normal file
@@ -0,0 +1,117 @@
|
||||
@namespace Oqtane.Modules.Admin.GlobalReplace
|
||||
@using System.Text.Json
|
||||
@inherits ModuleBase
|
||||
@inject ISiteTaskService SiteTaskService
|
||||
@inject IStringLocalizer<Index> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="find" HelpText="Specify the content which needs to be replaced" ResourceKey="Find">Find What: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="find" class="form-control" @bind="@_find" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="replace" HelpText="Specify the replacement content" ResourceKey="Replace">Replace With: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="replace" class="form-control" @bind="@_replace" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="casesensitive" HelpText="Specify if the replacement operation should be case sensitive" ResourceKey="CaseSensitive">Match Case? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="casesensitive" class="form-select" @bind="@_caseSensitive">
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="site" HelpText="Specify if site information should be updated (ie. name, head content, body content, settings)" ResourceKey="Site">Site Info? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="site" class="form-select" @bind="@_site">
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="pages" HelpText="Specify if page information should be updated (ie. name, title, head content, body content, settings)" ResourceKey="Pages">Page Info? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="pages" class="form-select" @bind="@_pages">
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="modules" HelpText="Specify if module information should be updated (ie. title, header, footer, settings)" ResourceKey="Modules">Module Info? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="modules" class="form-select" @bind="@_modules">
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="content" HelpText="Specify if module content should be updated" ResourceKey="Content">Module Content? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="content" class="form-select" @bind="@_content">
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br /><br />
|
||||
<ActionDialog Header="Global Replace" Message="This Operation is Permanent. Are You Sure You Wish To Proceed?" Action="Replace" Class="btn btn-primary" OnClick="@(async () => await Save())" ResourceKey="GlobalReplace" />
|
||||
<br /><br />
|
||||
|
||||
@code {
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
||||
public override string Title => "Global Replace";
|
||||
|
||||
private string _find;
|
||||
private string _replace;
|
||||
private string _caseSensitive = "True";
|
||||
private string _site = "True";
|
||||
private string _pages = "True";
|
||||
private string _modules = "True";
|
||||
private string _content = "True";
|
||||
|
||||
private async Task Save()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_find) && !string.IsNullOrEmpty(_replace))
|
||||
{
|
||||
var replace = new GlobalReplace
|
||||
{
|
||||
Find = _find,
|
||||
Replace = _replace,
|
||||
CaseSensitive = bool.Parse(_caseSensitive),
|
||||
Site = bool.Parse(_site),
|
||||
Pages = bool.Parse(_pages),
|
||||
Modules = bool.Parse(_modules),
|
||||
Content = bool.Parse(_content)
|
||||
};
|
||||
|
||||
var siteTask = new SiteTask(PageState.Site.SiteId, "Global Replace", "Oqtane.Infrastructure.GlobalReplaceTask, Oqtane.Server", JsonSerializer.Serialize(replace));
|
||||
await SiteTaskService.AddSiteTaskAsync(siteTask);
|
||||
|
||||
AddModuleMessage(Localizer["Success.Save"], MessageType.Success);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Saving Global Replace Settings {Error}", ex.Message);
|
||||
AddModuleMessage(Localizer["Error.Save"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -5,100 +5,109 @@
|
||||
@inject IStringLocalizer<Edit> 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="name" HelpText="Enter the job name" ResourceKey="Name">Name: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="name" class="form-control" @bind="@_name" maxlength="200" required />
|
||||
@if (_initialized)
|
||||
{
|
||||
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||
<div class="container">
|
||||
@if (!_editable)
|
||||
{
|
||||
<ModuleMessage Message="@Localizer["JobNotEditable"]" Type="MessageType.Warning" />
|
||||
}
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="type" HelpText="The fully qualified job type name" ResourceKey="Type">Type: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="type" class="form-control" @bind="@_jobType" required disabled />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="type" HelpText="The fully qualified job type name" ResourceKey="Type">Type: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="type" class="form-control" @bind="@_jobType" readonly />
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="name" HelpText="Enter the job name" ResourceKey="Name">Name: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="name" class="form-control" @bind="@_name" maxlength="200" required disabled="@(!_editable)" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="enabled" HelpText="Select whether you want the job enabled or not" ResourceKey="Enabled">Enabled? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="enabled" class="form-select" @bind="@_isEnabled" required>
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="enabled" HelpText="Select whether you want the job enabled or not" ResourceKey="Enabled">Enabled? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="enabled" class="form-select" @bind="@_isEnabled" required disabled="@(!_editable)">
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="runs-every" HelpText="Select how often you want the job to run" ResourceKey="RunsEvery">Runs Every: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="runs-every" class="form-control mb-1" @bind="@_interval" maxlength="4" required />
|
||||
<select id="runs-every" class="form-select" @bind="@_frequency" required>
|
||||
<option value="m">@Localizer["Minute(s)"]</option>
|
||||
<option value="H">@Localizer["Hour(s)"]</option>
|
||||
<option value="d">@Localizer["Day(s)"]</option>
|
||||
<option value="w">@Localizer["Week(s)"]</option>
|
||||
<option value="M">@Localizer["Month(s)"]</option>
|
||||
<option value="O">@Localizer["Once"]</option>
|
||||
</select>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="runs-every" HelpText="Select how often you want the job to run" ResourceKey="RunsEvery">Runs Every: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="runs-every" class="form-control mb-1" @bind="@_interval" maxlength="4" required disabled="@(!_editable)" />
|
||||
<select id="runs-every" class="form-select" @bind="@_frequency" required disabled="@(!_editable)">
|
||||
<option value="m">@Localizer["Minute(s)"]</option>
|
||||
<option value="H">@Localizer["Hour(s)"]</option>
|
||||
<option value="d">@Localizer["Day(s)"]</option>
|
||||
<option value="w">@Localizer["Week(s)"]</option>
|
||||
<option value="M">@Localizer["Month(s)"]</option>
|
||||
<option value="O">@Localizer["Once"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="retention" HelpText="Number of log entries to retain for this job" ResourceKey="RetentionLog">Retention Log (Items): </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="retention" type="number" min="0" step ="1" class="form-control" @bind="@_retentionHistory" maxlength="3" required />
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="retention" HelpText="Number of log entries to retain for this job" ResourceKey="RetentionLog">Retention Log (Items): </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="retention" type="number" min="0" step="1" class="form-control" @bind="@_retentionHistory" maxlength="3" required disabled="@(!_editable)" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="starting" HelpText="Optionally enter the date and time when this job should start executing" ResourceKey="Starting">Starting: </Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<input id="starting" type="date" class="form-control" @bind="@_startDate" />
|
||||
</div>
|
||||
<div class="col">
|
||||
<input id="starting" type="time" class="form-control" @bind="@_startTime" placeholder="hh:mm" required="@(_startDate.HasValue)" />
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="starting" HelpText="Optionally enter the date and time when this job should start executing" ResourceKey="Starting">Starting: </Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<input id="starting" type="date" class="form-control" @bind="@_startDate" disabled="@(!_editable)" />
|
||||
</div>
|
||||
<div class="col">
|
||||
<input id="starting" type="time" class="form-control" @bind="@_startTime" placeholder="hh:mm" required="@(_startDate.HasValue)" disabled="@(!_editable)" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="ending" HelpText="Optionally enter the date and time when this job should stop executing" ResourceKey="Ending">Ending: </Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<input id="ending" type="date" class="form-control" @bind="@_endDate" />
|
||||
</div>
|
||||
<div class="col">
|
||||
<input id="ending" type="time" class="form-control" placeholder="hh:mm" @bind="@_endTime" required="@(_endDate.HasValue)" />
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="ending" HelpText="Optionally enter the date and time when this job should stop executing" ResourceKey="Ending">Ending: </Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<input id="ending" type="date" class="form-control" @bind="@_endDate" disabled="@(!_editable)" />
|
||||
</div>
|
||||
<div class="col">
|
||||
<input id="ending" type="time" class="form-control" placeholder="hh:mm" @bind="@_endTime" required="@(_endDate.HasValue)" disabled="@(!_editable)" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="next" HelpText="Optionally modify the date and time when this job should execute next" ResourceKey="NextExecution">Next Execution: </Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<input id="next" type="date" class="form-control" @bind="@_nextDate" />
|
||||
</div>
|
||||
<div class="col">
|
||||
<input id="next" type="time" class="form-control" placeholder="hh:mm" @bind="@_nextTime" required="@(_nextDate.HasValue)" />
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="next" HelpText="The date and time when this job will execute next. This value cannot be modified. Use the settings above to control the execution of the job." ResourceKey="NextExecution">Next Execution: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="next" class="form-control" @bind="@_nextDate" disabled />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="SaveJob">@SharedLocalizer["Save"]</button>
|
||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||
<br />
|
||||
<br />
|
||||
<AuditInfo CreatedBy="@createdby" CreatedOn="@createdon" ModifiedBy="@modifiedby" ModifiedOn="@modifiedon"></AuditInfo>
|
||||
</form>
|
||||
<br />
|
||||
@if (_editable)
|
||||
{
|
||||
<button type="button" class="btn btn-success" @onclick="SaveJob">@SharedLocalizer["Save"]</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button type="button" class="btn btn-danger" @onclick="DisableJob">@Localizer["Disable"]</button>
|
||||
}
|
||||
<NavLink class="btn btn-secondary ms-1" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||
<br />
|
||||
<br />
|
||||
<AuditInfo CreatedBy="@createdby" CreatedOn="@createdon" ModifiedBy="@modifiedby" ModifiedOn="@modifiedon"></AuditInfo>
|
||||
</form>
|
||||
}
|
||||
|
||||
@code {
|
||||
private ElementReference form;
|
||||
private bool validated = false;
|
||||
private bool _initialized = false;
|
||||
private bool _editable = true;
|
||||
private int _jobId;
|
||||
private string _name = string.Empty;
|
||||
private string _jobType = string.Empty;
|
||||
@@ -113,26 +122,32 @@
|
||||
private DateTime? _nextDate = null;
|
||||
private DateTime? _nextTime = null;
|
||||
private string createdby;
|
||||
private DateTime createdon;
|
||||
private string modifiedby;
|
||||
private DateTime modifiedon;
|
||||
private DateTime createdon;
|
||||
private string modifiedby;
|
||||
private DateTime modifiedon;
|
||||
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
_jobId = Int32.Parse(PageState.QueryString["id"]);
|
||||
Job job = await JobService.GetJobAsync(_jobId);
|
||||
if (job != null)
|
||||
{
|
||||
_name = job.Name;
|
||||
_jobType = job.JobType;
|
||||
_isEnabled = job.IsEnabled.ToString();
|
||||
_interval = job.Interval.ToString();
|
||||
_frequency = job.Frequency;
|
||||
_startDate = UtcToLocal(job.StartDate);
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await LoadJob();
|
||||
}
|
||||
|
||||
protected async Task LoadJob()
|
||||
{
|
||||
try
|
||||
{
|
||||
_jobId = Int32.Parse(PageState.QueryString["id"]);
|
||||
Job job = await JobService.GetJobAsync(_jobId);
|
||||
if (job != null)
|
||||
{
|
||||
_editable = !job.IsEnabled && !job.IsExecuting;
|
||||
_name = job.Name;
|
||||
_jobType = job.JobType;
|
||||
_isEnabled = job.IsEnabled.ToString();
|
||||
_interval = job.Interval.ToString();
|
||||
_frequency = job.Frequency;
|
||||
_startDate = UtcToLocal(job.StartDate);
|
||||
_startTime = UtcToLocal(job.StartDate);
|
||||
_endDate = UtcToLocal(job.EndDate);
|
||||
_endTime = UtcToLocal(job.EndDate);
|
||||
@@ -140,70 +155,107 @@
|
||||
_nextDate = UtcToLocal(job.NextExecution);
|
||||
_nextTime = UtcToLocal(job.NextExecution);
|
||||
createdby = job.CreatedBy;
|
||||
createdon = job.CreatedOn;
|
||||
modifiedby = job.ModifiedBy;
|
||||
modifiedon = job.ModifiedOn;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Loading Job {JobId} {Error}", _jobId, ex.Message);
|
||||
AddModuleMessage(Localizer["Error.Job.Load"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
createdon = job.CreatedOn;
|
||||
modifiedby = job.ModifiedBy;
|
||||
modifiedon = job.ModifiedOn;
|
||||
}
|
||||
|
||||
private async Task SaveJob()
|
||||
{
|
||||
if (!Utilities.ValidateEffectiveExpiryDates(_startDate, _endDate))
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.StartEndDateError"], MessageType.Warning);
|
||||
return;
|
||||
_initialized = true;
|
||||
}
|
||||
validated = true;
|
||||
var interop = new Interop(JSRuntime);
|
||||
if (await interop.FormValid(form))
|
||||
{
|
||||
var job = await JobService.GetJobAsync(_jobId);
|
||||
job.Name = _name;
|
||||
job.JobType = _jobType;
|
||||
job.IsEnabled = Boolean.Parse(_isEnabled);
|
||||
job.Frequency = _frequency;
|
||||
if (job.Frequency == "O") // once
|
||||
{
|
||||
job.Interval = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
job.Interval = int.Parse(_interval);
|
||||
}
|
||||
job.StartDate = _startDate.HasValue && _startTime.HasValue
|
||||
? LocalToUtc(_startDate.GetValueOrDefault().Date.Add(_startTime.GetValueOrDefault().TimeOfDay))
|
||||
: null;
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Loading Job {JobId} {Error}", _jobId, ex.Message);
|
||||
AddModuleMessage(Localizer["Error.Job.Load"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
job.EndDate = _endDate.HasValue && _endTime.HasValue
|
||||
? LocalToUtc(_endDate.GetValueOrDefault().Date.Add(_endTime.GetValueOrDefault().TimeOfDay))
|
||||
: null;
|
||||
private async Task SaveJob()
|
||||
{
|
||||
try
|
||||
{
|
||||
var interop = new Interop(JSRuntime);
|
||||
if (await interop.FormValid(form))
|
||||
{
|
||||
var job = await JobService.GetJobAsync(_jobId);
|
||||
job.Name = _name;
|
||||
job.IsEnabled = bool.Parse(_isEnabled);
|
||||
|
||||
job.NextExecution = _nextDate.HasValue && _nextTime.HasValue
|
||||
? LocalToUtc(_nextDate.GetValueOrDefault().Date.Add(_nextTime.GetValueOrDefault().TimeOfDay))
|
||||
: null;
|
||||
job.RetentionHistory = int.Parse(_retentionHistory);
|
||||
job.Frequency = _frequency;
|
||||
if (job.Frequency == "O") // once
|
||||
{
|
||||
job.Interval = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
job.Interval = int.Parse(_interval);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
job = await JobService.UpdateJobAsync(job);
|
||||
await logger.LogInformation("Job Updated {Job}", job);
|
||||
NavigationManager.NavigateTo(NavigateUrl());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Udate Job {Job} {Error}", job, ex.Message);
|
||||
AddModuleMessage(Localizer["Error.Job.Update"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.Required.JobInfo"], MessageType.Warning);
|
||||
}
|
||||
}
|
||||
job.StartDate = _startDate.HasValue && _startTime.HasValue
|
||||
? LocalToUtc(_startDate.GetValueOrDefault().Date.Add(_startTime.GetValueOrDefault().TimeOfDay))
|
||||
: null;
|
||||
|
||||
job.EndDate = _endDate.HasValue && _endTime.HasValue
|
||||
? LocalToUtc(_endDate.GetValueOrDefault().Date.Add(_endTime.GetValueOrDefault().TimeOfDay))
|
||||
: null;
|
||||
|
||||
job.RetentionHistory = int.Parse(_retentionHistory);
|
||||
|
||||
if (!job.IsEnabled || Utilities.ValidateEffectiveExpiryDates(job.StartDate, job.EndDate))
|
||||
{
|
||||
if (!job.IsEnabled || (job.StartDate >= DateTime.UtcNow || job.StartDate == null))
|
||||
{
|
||||
job.NextExecution = null;
|
||||
job = await JobService.UpdateJobAsync(job);
|
||||
await logger.LogInformation("Job Updated {Job}", job);
|
||||
NavigationManager.NavigateTo(NavigateUrl());
|
||||
}
|
||||
else
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.StartDateError"], MessageType.Warning);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.StartEndDateError"], MessageType.Warning);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.Required.JobInfo"], MessageType.Warning);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Updating Job {JobId} {Error}", _jobId, ex.Message);
|
||||
AddModuleMessage(Localizer["Error.Job.Update"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DisableJob()
|
||||
{
|
||||
try
|
||||
{
|
||||
var job = await JobService.GetJobAsync(_jobId);
|
||||
if (job != null)
|
||||
{
|
||||
if (job.IsExecuting)
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.ExecutingError"], MessageType.Warning);
|
||||
}
|
||||
else
|
||||
{
|
||||
job.IsEnabled = false;
|
||||
job.NextExecution = null;
|
||||
job = await JobService.UpdateJobAsync(job);
|
||||
await logger.LogInformation("Job Updated {Job}", job);
|
||||
NavigationManager.NavigateTo(NavigateUrl());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Updating Job {JobId} {Error}", _jobId, ex.Message);
|
||||
AddModuleMessage(Localizer["Error.Job.Update"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,96 +14,136 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
@if (!twofactor)
|
||||
{
|
||||
<form @ref="login" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||
<div class="Oqtane-Modules-Admin-Login" @onkeypress="@(e => KeyPressed(e))">
|
||||
@if (_allowexternallogin)
|
||||
{
|
||||
<button type="button" class="btn btn-primary col-12" @onclick="ExternalLogin">@Localizer["Use"] @PageState.Site.Settings["ExternalLogin:ProviderName"]</button>
|
||||
<hr class="app-rule mt-3 mb-2" />
|
||||
}
|
||||
@if (_allowsitelogin)
|
||||
{
|
||||
<div class="form-group text-center">
|
||||
<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" @bind:event="oninput" required />
|
||||
</div>
|
||||
<div class="form-group text-center mt-2">
|
||||
<Label Class="control-label" For="password" HelpText="Please enter your Password" ResourceKey="Password">Password:</Label>
|
||||
<div class="input-group">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
@if (!_alwaysremember)
|
||||
<form @ref="login" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||
<div class="Oqtane-Modules-Admin-Login" @onkeypress="@(e => KeyPressed(e))">
|
||||
@switch (_action)
|
||||
{
|
||||
case "Login":
|
||||
@if (_allowexternallogin)
|
||||
{
|
||||
<button type="button" class="btn btn-primary col-12" @onclick="ExternalLogin">@Localizer["Use"] @PageState.Site.Settings["ExternalLogin:ProviderName"]</button>
|
||||
<hr class="app-rule mt-3 mb-2" />
|
||||
}
|
||||
@if (_allowsitelogin)
|
||||
{
|
||||
<div class="form-group text-center">
|
||||
<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" @bind:event="oninput" required />
|
||||
</div>
|
||||
<div class="form-group text-center mt-2">
|
||||
<div>
|
||||
<input id="remember" type="checkbox" class="form-check-input" @bind="@_remember" />
|
||||
<Label Class="control-label" For="remember" HelpText="Specify if you would like to be signed back in automatically the next time you visit this site" ResourceKey="Remember">Remember Me?</Label>
|
||||
<Label Class="control-label" For="password" HelpText="Please enter your Password" ResourceKey="Password">Password:</Label>
|
||||
<div class="input-group">
|
||||
<input id="password" type="@_passwordtype" @ref="password" 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>
|
||||
</div>
|
||||
</div>
|
||||
@if (!_alwaysremember)
|
||||
{
|
||||
<div class="form-group text-center mt-2">
|
||||
<div>
|
||||
<input id="remember" type="checkbox" class="form-check-input" @bind="@_remember" />
|
||||
<Label Class="control-label" For="remember" HelpText="Specify if you would like to be signed back in automatically the next time you visit this site" ResourceKey="Remember">Stay Signed In?</Label>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="btn-group mt-2 col-12" role="group">
|
||||
<button type="button" class="btn btn-primary col-6" @onclick="Login">@SharedLocalizer["Login"]</button>
|
||||
<button type="button" class="btn btn-secondary col-6" @onclick="CancelLogin">@SharedLocalizer["Cancel"]</button>
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn btn-secondary col-12 mt-4" @onclick="@(() => SetAction("ForgotPassword"))">@Localizer["ForgotPassword"]</button>
|
||||
}
|
||||
|
||||
<div class="btn-group mt-2 col-12" role="group">
|
||||
<button type="button" class="btn btn-primary col-6" @onclick="Login">@SharedLocalizer["Login"]</button>
|
||||
<button type="button" class="btn btn-secondary col-6" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||
</div>
|
||||
<button type="button" class="btn btn-secondary col-12 mt-4" @onclick="Forgot">@Localizer["ForgotPassword"]</button>
|
||||
@if (_allowloginlink)
|
||||
{
|
||||
<hr class="app-rule mt-3" />
|
||||
<button type="button" class="btn btn-primary col-12 mt-2" @onclick="@(() => SetAction("LoginLink"))">@Localizer["UseLoginLink"]</button>
|
||||
}
|
||||
|
||||
@if (_allowpasskeys)
|
||||
{
|
||||
<hr class="app-rule mt-3" />
|
||||
<button type="button" class="btn btn-primary col-12 mt-2" @onclick="Passkey">@Localizer["Passkey"]</button>
|
||||
<button type="button" class="btn btn-primary col-12 mt-2" @onclick="PasskeyLogin">@Localizer["Passkey"]</button>
|
||||
}
|
||||
|
||||
@if (PageState.Site.AllowRegistration)
|
||||
{
|
||||
{
|
||||
<hr class="app-rule mt-3" />
|
||||
<div class="text-center mt-2">
|
||||
<NavLink href="@_registerurl">@Localizer["Register"]</NavLink>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
else
|
||||
{
|
||||
<form @ref="login" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||
<div class="container Oqtane-Modules-Admin-Login">
|
||||
<div class="form-group">
|
||||
<Label Class="control-label" For="code" HelpText="Please enter the secure verification code which was sent to you by email" ResourceKey="Code">Verification Code:</Label>
|
||||
<input id="code" class="form-control" @bind="@_code" placeholder="@Localizer["Code.Placeholder"]" maxlength="6" required />
|
||||
</div>
|
||||
<br />
|
||||
<button type="button" class="btn btn-primary" @onclick="Login">@SharedLocalizer["Login"]</button>
|
||||
<button type="button" class="btn btn-secondary" @onclick="Reset">@SharedLocalizer["Cancel"]</button>
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
break;
|
||||
case "ForgotPassword":
|
||||
<div class="form-group text-center">
|
||||
<Label Class="control-label" For="username" HelpText="Please enter your Username" ResourceKey="Username">Username:</Label>
|
||||
<input id="username" type="text" class="form-control" placeholder="@Localizer["Username.Placeholder"]" @bind="@_username" @bind:event="oninput" required />
|
||||
</div>
|
||||
<div class="btn-group mt-4 col-12" role="group">
|
||||
<button type="button" class="btn btn-primary col-6" @onclick="ForgotPassword">@SharedLocalizer["Send"]</button>
|
||||
<button type="button" class="btn btn-secondary col-6" @onclick="@(() => SetAction("Login"))">@SharedLocalizer["Cancel"]</button>
|
||||
</div>
|
||||
<button type="button" class="btn btn-secondary col-12 mt-4" @onclick="@(() => SetAction("ForgotUsername"))">@Localizer["ForgotUsername"]</button>
|
||||
break;
|
||||
case "ForgotUsername":
|
||||
<div class="form-group text-center">
|
||||
<Label Class="control-label" For="email" HelpText="Please enter your Email" ResourceKey="Email">Email:</Label>
|
||||
<input id="email" type="text" class="form-control" placeholder="@Localizer["Email.Placeholder"]" @bind="@_email" required />
|
||||
</div>
|
||||
<div class="btn-group mt-4 col-12" role="group">
|
||||
<button type="button" class="btn btn-primary col-6" @onclick="ForgotUsername">@SharedLocalizer["Send"]</button>
|
||||
<button type="button" class="btn btn-secondary col-6" @onclick="@(() => SetAction("Login"))">@SharedLocalizer["Cancel"]</button>
|
||||
</div>
|
||||
break;
|
||||
case "LoginLink":
|
||||
<div class="form-group text-center">
|
||||
<Label Class="control-label" For="email" HelpText="Please enter your Email" ResourceKey="Email">Email:</Label>
|
||||
<input id="email" type="text" class="form-control" placeholder="@Localizer["Email.Placeholder"]" @bind="@_email" required />
|
||||
</div>
|
||||
<div class="btn-group mt-4 col-12" role="group">
|
||||
<button type="button" class="btn btn-primary col-6" @onclick="LoginLink">@SharedLocalizer["Send"]</button>
|
||||
<button type="button" class="btn btn-secondary col-6" @onclick="@(() => SetAction("Login"))">@SharedLocalizer["Cancel"]</button>
|
||||
</div>
|
||||
break;
|
||||
case "TwoFactor":
|
||||
<div class="form-group">
|
||||
<Label Class="control-label" For="code" HelpText="Please enter the secure verification code which was sent to you by email" ResourceKey="Code">Verification Code:</Label>
|
||||
<input id="code" class="form-control" @bind="@_code" placeholder="@Localizer["Code.Placeholder"]" maxlength="6" required />
|
||||
</div>
|
||||
<div class="btn-group mt-4 col-12" role="group">
|
||||
<button type="button" class="btn btn-primary" @onclick="Login">@SharedLocalizer["Login"]</button>
|
||||
<button type="button" class="btn btn-secondary" @onclick="@(() => SetAction("Login"))">@SharedLocalizer["Cancel"]</button>
|
||||
</div>
|
||||
break;
|
||||
}
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
|
||||
@code {
|
||||
private bool _allowsitelogin = true;
|
||||
private string _action = "Login";
|
||||
private bool _allowexternallogin = false;
|
||||
private bool _allowsitelogin = true;
|
||||
private bool _allowloginlink = false;
|
||||
private bool _allowpasskeys = false;
|
||||
private string _returnurl = string.Empty;
|
||||
|
||||
private ElementReference login;
|
||||
private bool validated = false;
|
||||
private bool twofactor = false;
|
||||
private string _username = string.Empty;
|
||||
private ElementReference username;
|
||||
private string _password = string.Empty;
|
||||
private string _passwordtype = "password";
|
||||
private string _togglepassword = string.Empty;
|
||||
private ElementReference password;
|
||||
private bool _remember = false;
|
||||
private bool _alwaysremember = false;
|
||||
private string _code = string.Empty;
|
||||
private string _registerurl = string.Empty;
|
||||
private string _email = string.Empty;
|
||||
private string _code = string.Empty;
|
||||
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Anonymous;
|
||||
public override bool? Prerender => true;
|
||||
|
||||
public override List<Resource> Resources => new List<Resource>()
|
||||
{
|
||||
@@ -116,6 +156,7 @@ else
|
||||
{
|
||||
_allowexternallogin = (SettingService.GetSetting(PageState.Site.Settings, "ExternalLogin:ProviderType", "") != "") ? true : false;
|
||||
_allowsitelogin = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AllowSiteLogin", "true"));
|
||||
_allowloginlink = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:LoginLink", "false"));
|
||||
_allowpasskeys = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:Passkeys", "false"));
|
||||
_alwaysremember = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AlwaysRemember", "false"));
|
||||
|
||||
@@ -128,6 +169,9 @@ else
|
||||
_registerurl = NavigateUrl("register");
|
||||
}
|
||||
|
||||
// PageState.ReturnUrl is not specified if user navigated directly to login page
|
||||
_returnurl = (!string.IsNullOrEmpty(PageState.ReturnUrl)) ? PageState.ReturnUrl : PageState.Alias.Path;
|
||||
|
||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||
|
||||
if (PageState.QueryString.ContainsKey("name"))
|
||||
@@ -175,7 +219,15 @@ else
|
||||
{
|
||||
if (PageState.QueryString.ContainsKey("status"))
|
||||
{
|
||||
AddModuleMessage(Localizer["ExternalLoginStatus." + PageState.QueryString["status"]], MessageType.Info);
|
||||
AddModuleMessage(Localizer["ExternalLoginStatus." + PageState.QueryString["status"]], MessageType.Warning);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_allowexternallogin && !_allowsitelogin)
|
||||
{
|
||||
// external login
|
||||
ExternalLogin();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -186,6 +238,48 @@ else
|
||||
}
|
||||
}
|
||||
|
||||
private async Task KeyPressed(KeyboardEventArgs e)
|
||||
{
|
||||
if (e.Code == "Enter" || e.Code == "NumpadEnter")
|
||||
{
|
||||
switch (_action)
|
||||
{
|
||||
case "Login":
|
||||
await Login();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SetAction(string action)
|
||||
{
|
||||
_action = action;
|
||||
_username = "";
|
||||
_password = "";
|
||||
_email = "";
|
||||
ClearModuleMessage();
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private void ExternalLogin()
|
||||
{
|
||||
NavigationManager.NavigateTo(Utilities.TenantUrl(PageState.Alias, "/pages/external?returnurl=" + WebUtility.UrlEncode(_returnurl)), true);
|
||||
}
|
||||
|
||||
private void TogglePassword()
|
||||
{
|
||||
if (_passwordtype == "password")
|
||||
{
|
||||
_passwordtype = "text";
|
||||
_togglepassword = SharedLocalizer["HidePassword"];
|
||||
}
|
||||
else
|
||||
{
|
||||
_passwordtype = "password";
|
||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Login()
|
||||
{
|
||||
try
|
||||
@@ -197,7 +291,7 @@ else
|
||||
var hybrid = (PageState.Runtime == Shared.Runtime.Hybrid);
|
||||
var user = new User { SiteId = PageState.Site.SiteId, Username = _username, Password = _password, LastIPAddress = SiteState.RemoteIPAddress};
|
||||
|
||||
if (!twofactor)
|
||||
if (_action == "Login")
|
||||
{
|
||||
_remember = _alwaysremember || _remember;
|
||||
user = await UserService.LoginUserAsync(user, hybrid, _remember);
|
||||
@@ -211,20 +305,17 @@ else
|
||||
{
|
||||
await logger.LogInformation(LogFunction.Security, "Login Successful For {Username} From IP Address {IPAddress}", _username, SiteState.RemoteIPAddress);
|
||||
|
||||
// return url is not specified if user navigated directly to login page
|
||||
var returnurl = (!string.IsNullOrEmpty(PageState.ReturnUrl)) ? PageState.ReturnUrl : PageState.Alias.Path;
|
||||
|
||||
if (hybrid)
|
||||
{
|
||||
// hybrid apps utilize an interactive login
|
||||
var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider.GetService(typeof(IdentityAuthenticationStateProvider));
|
||||
authstateprovider.NotifyAuthenticationChanged();
|
||||
NavigationManager.NavigateTo(NavigateUrl(returnurl, true));
|
||||
NavigationManager.NavigateTo(NavigateUrl(_returnurl, true));
|
||||
}
|
||||
else
|
||||
{
|
||||
// post back to the Login page so that the cookies are set correctly
|
||||
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, username = _username, password = _password, remember = _remember, returnurl = WebUtility.UrlEncode(returnurl) };
|
||||
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, username = _username, password = _password, remember = _remember, returnurl = WebUtility.UrlEncode(_returnurl) };
|
||||
string url = Utilities.TenantUrl(PageState.Alias, "/pages/login/");
|
||||
await interop.SubmitForm(url, fields);
|
||||
}
|
||||
@@ -233,13 +324,13 @@ else
|
||||
{
|
||||
if (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:TwoFactor", "false") == "required" || (user != null && user.TwoFactorRequired))
|
||||
{
|
||||
twofactor = true;
|
||||
_action = "TwoFactor";
|
||||
validated = false;
|
||||
AddModuleMessage(Localizer["Message.TwoFactor"], MessageType.Info);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!twofactor)
|
||||
if (_action != "TwoFactor")
|
||||
{
|
||||
await logger.LogInformation(LogFunction.Security, "Login Failed For Username {Username}", _username);
|
||||
AddModuleMessage(Localizer["Error.Login.Fail"], MessageType.Error);
|
||||
@@ -264,23 +355,30 @@ else
|
||||
}
|
||||
}
|
||||
|
||||
private void Cancel()
|
||||
private void CancelLogin()
|
||||
{
|
||||
NavigationManager.NavigateTo(PageState.ReturnUrl);
|
||||
NavigationManager.NavigateTo(_returnurl);
|
||||
}
|
||||
|
||||
private async Task Forgot()
|
||||
private async Task PasskeyLogin()
|
||||
{
|
||||
// post back to the Passkey page so that the cookies are set correctly
|
||||
var interop = new Interop(JSRuntime);
|
||||
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, operation = "request", returnurl = _returnurl };
|
||||
string url = Utilities.TenantUrl(PageState.Alias, "/pages/passkey/");
|
||||
await interop.SubmitForm(url, fields);
|
||||
}
|
||||
|
||||
private async Task ForgotPassword()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_username != string.Empty)
|
||||
if (!string.IsNullOrEmpty(_username))
|
||||
{
|
||||
var user = await UserService.GetUserAsync(_username, PageState.Site.SiteId);
|
||||
if (user != null)
|
||||
if (await UserService.ForgotPasswordAsync(_username))
|
||||
{
|
||||
await UserService.ForgotPasswordAsync(user);
|
||||
await logger.LogInformation(LogFunction.Security, "Password Reset Notification Sent For Username {Username}", _username);
|
||||
AddModuleMessage(Localizer["Message.ForgotUser"], MessageType.Info);
|
||||
AddModuleMessage(Localizer["Message.ForgotPassword"], MessageType.Info);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -289,10 +387,8 @@ else
|
||||
}
|
||||
else
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.ForgotPassword"], MessageType.Info);
|
||||
AddModuleMessage(Localizer["Message.Required.UserInfo"], MessageType.Warning);
|
||||
}
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -301,51 +397,62 @@ else
|
||||
}
|
||||
}
|
||||
|
||||
private void Reset()
|
||||
private async Task ForgotUsername()
|
||||
{
|
||||
twofactor = false;
|
||||
_username = "";
|
||||
_password = "";
|
||||
ClearModuleMessage();
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task KeyPressed(KeyboardEventArgs e)
|
||||
{
|
||||
if (e.Code == "Enter" || e.Code == "NumpadEnter")
|
||||
try
|
||||
{
|
||||
await Login();
|
||||
if (!string.IsNullOrEmpty(_email))
|
||||
{
|
||||
if (await UserService.ForgotUsernameAsync(_email))
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.ForgotUsername"], MessageType.Info);
|
||||
await logger.LogInformation(LogFunction.Security, "Username Reminder Notification Sent For Email {Email}", _email);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.UserDoesNotExist"], MessageType.Warning);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.Required.UserInfo"], MessageType.Warning);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Sending Username Reminder {Error}", ex.Message);
|
||||
AddModuleMessage(Localizer["Error.ForgotUsername"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void TogglePassword()
|
||||
private async Task LoginLink()
|
||||
{
|
||||
if (_passwordtype == "password")
|
||||
try
|
||||
{
|
||||
_passwordtype = "text";
|
||||
_togglepassword = SharedLocalizer["HidePassword"];
|
||||
if (!string.IsNullOrEmpty(_email))
|
||||
{
|
||||
if (await UserService.SendLoginLinkAsync(_email, _returnurl))
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.SendLoginLink"], MessageType.Info);
|
||||
await logger.LogInformation(LogFunction.Security, "Login Link Sent To Email {Email}", _email);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.UserDoesNotExist"], MessageType.Warning);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.Required.UserInfo"], MessageType.Warning);
|
||||
}
|
||||
}
|
||||
else
|
||||
catch (Exception ex)
|
||||
{
|
||||
_passwordtype = "password";
|
||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||
await logger.LogError(ex, "Error Sending Login Link {Error}", ex.Message);
|
||||
AddModuleMessage(Localizer["Error.SendLoginLink"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void ExternalLogin()
|
||||
{
|
||||
NavigationManager.NavigateTo(Utilities.TenantUrl(PageState.Alias, "/pages/external?returnurl=" + WebUtility.UrlEncode(PageState.ReturnUrl)), true);
|
||||
}
|
||||
|
||||
private async Task Passkey()
|
||||
{
|
||||
// post back to the Passkey page so that the cookies are set correctly
|
||||
var interop = new Interop(JSRuntime);
|
||||
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, operation = "request", returnurl = NavigateUrl() };
|
||||
string url = Utilities.TenantUrl(PageState.Alias, "/pages/passkey/");
|
||||
await interop.SubmitForm(url, fields);
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender && PageState.QueryString.ContainsKey("options"))
|
||||
@@ -358,8 +465,7 @@ else
|
||||
if (!string.IsNullOrEmpty(credential))
|
||||
{
|
||||
// post back to the Passkey page so that the cookies are set correctly
|
||||
var returnurl = (!string.IsNullOrEmpty(PageState.ReturnUrl)) ? PageState.ReturnUrl : PageState.Alias.Path + "/";
|
||||
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, operation = "login", credential = credential, returnurl = returnurl };
|
||||
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, operation = "login", credential = credential, returnurl = _returnurl };
|
||||
string url = Utilities.TenantUrl(PageState.Alias, "/pages/passkey/");
|
||||
await interop.SubmitForm(url, fields);
|
||||
}
|
||||
@@ -377,18 +483,28 @@ else
|
||||
return;
|
||||
}
|
||||
|
||||
if (firstRender && PageState.User == null && _allowsitelogin)
|
||||
if (firstRender && PageState.User == null && _allowsitelogin && _action == "Login")
|
||||
{
|
||||
if (!string.IsNullOrEmpty(username.Id)) // ensure username is visible in UI
|
||||
if (string.IsNullOrEmpty(_username))
|
||||
{
|
||||
await username.FocusAsync();
|
||||
if (!string.IsNullOrEmpty(username.Id)) // ensure username is visible in UI
|
||||
{
|
||||
await username.FocusAsync();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!string.IsNullOrEmpty(password.Id)) // ensure password is visible in UI
|
||||
{
|
||||
await password.FocusAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// redirect logged in user to specified page
|
||||
if (PageState.User != null && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||
{
|
||||
NavigationManager.NavigateTo(PageState.ReturnUrl);
|
||||
NavigationManager.NavigateTo(_returnurl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
@namespace Oqtane.Modules.Admin.ModuleDefinitions
|
||||
@inherits ModuleBase
|
||||
@using System.Text.RegularExpressions
|
||||
@using System.Reflection
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IModuleDefinitionService ModuleDefinitionService
|
||||
@inject IModuleService ModuleService
|
||||
@@ -97,6 +98,16 @@
|
||||
{
|
||||
AddModuleMessage(Localizer["Info.Module.Development"], MessageType.Info);
|
||||
}
|
||||
else
|
||||
{
|
||||
var entryAssemblyName = Assembly.GetEntryAssembly().GetName().Name;
|
||||
if (entryAssemblyName.EndsWith(".Oqtane"))
|
||||
{
|
||||
// Oqtane Application assemblies end with .Server.Oqtane or .Client.Oqtane
|
||||
string[] segments = entryAssemblyName.Split('.');
|
||||
_owner = string.Join(".", segments, 0, segments.Length - 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
|
||||
@@ -46,7 +46,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<Section Name="Information" ResourceKey="Information">
|
||||
<br />
|
||||
<Section Name="Information" ResourceKey="Information">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="moduledefinitionname" HelpText="The internal name of the module" ResourceKey="InternalName">Internal Name: </Label>
|
||||
@@ -97,7 +98,13 @@
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="fingerprint" HelpText="A unique identifier for the module's static resources. This value can be changed by clicking the Save option below (ie. cache busting)." ResourceKey="Fingerprint">Fingerprint: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="fingerprint" class="form-control" @bind="@_fingerprint" disabled />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="SaveModuleDefinition">@SharedLocalizer["Save"]</button>
|
||||
@@ -231,6 +238,7 @@
|
||||
private string _url = "";
|
||||
private string _contact = "";
|
||||
private string _license = "";
|
||||
private string _fingerprint = "";
|
||||
private List<Permission> _permissions = null;
|
||||
private string _createdby;
|
||||
private DateTime _createdon;
|
||||
@@ -266,6 +274,7 @@
|
||||
_url = moduleDefinition.Url;
|
||||
_contact = moduleDefinition.Contact;
|
||||
_license = moduleDefinition.License;
|
||||
_fingerprint = moduleDefinition.Fingerprint;
|
||||
_permissions = moduleDefinition.PermissionList;
|
||||
_createdby = moduleDefinition.CreatedBy;
|
||||
_createdon = moduleDefinition.CreatedOn;
|
||||
|
||||
@@ -130,7 +130,7 @@ else
|
||||
|
||||
private async Task LoadModuleDefinitions()
|
||||
{
|
||||
_moduleDefinitions = _allModuleDefinitions.Where(item => item.Categories.Contains(_category)).ToList();
|
||||
_moduleDefinitions = _allModuleDefinitions.Where(item => item.Categories.Split(',', StringSplitOptions.RemoveEmptyEntries).Contains(_category)).ToList();
|
||||
_packages = await PackageService.GetPackageUpdatesAsync("module");
|
||||
}
|
||||
|
||||
|
||||
@@ -73,27 +73,29 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="page" HelpText="The page that the module is located on" ResourceKey="Page">Page: </Label>
|
||||
<Label Class="col-sm-3" For="page" HelpText="The page that the module is located on. Please note that shared modules cannot be moved to other pages." ResourceKey="Page">Page: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="page" class="form-select" @bind="@_pageId" required>
|
||||
@if (PageState.Page.UserId != null)
|
||||
{
|
||||
@if (PageState.Page.UserId != null || _isShared)
|
||||
{
|
||||
<select id="page" class="form-select" @bind="@_pageId" required disabled>
|
||||
<option value="@PageState.Page.PageId">@(PageState.Page.Name)</option>
|
||||
}
|
||||
else
|
||||
</select>
|
||||
}
|
||||
else
|
||||
{
|
||||
<select id="page" class="form-select" @bind="@_pageId" required>
|
||||
@if (_pages != null)
|
||||
{
|
||||
if (_pages != null)
|
||||
foreach (Page p in _pages)
|
||||
{
|
||||
foreach (Page p in _pages)
|
||||
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, p.PermissionList))
|
||||
{
|
||||
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, p.PermissionList))
|
||||
{
|
||||
<option value="@p.PageId">@(new string('-', p.Level * 2))@(p.Name)</option>
|
||||
}
|
||||
<option value="@p.PageId">@(new string('-', p.Level * 2))@(p.Name)</option>
|
||||
}
|
||||
}
|
||||
}
|
||||
</select>
|
||||
</select>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -161,6 +163,7 @@
|
||||
private string _pane;
|
||||
private string _containerType;
|
||||
private string _allPages = "false";
|
||||
private bool _isShared = false;
|
||||
private string _header = "";
|
||||
private string _footer = "";
|
||||
private string _permissionNames = "";
|
||||
@@ -207,6 +210,7 @@
|
||||
_expirydate = Utilities.UtcAsLocalDate(pagemodule.ExpiryDate);
|
||||
|
||||
_allPages = pagemodule.Module.AllPages.ToString();
|
||||
_isShared = pagemodule.Module.IsShared;
|
||||
createdby = pagemodule.Module.CreatedBy;
|
||||
createdon = pagemodule.Module.CreatedOn;
|
||||
modifiedby = pagemodule.Module.ModifiedBy;
|
||||
@@ -276,15 +280,40 @@
|
||||
var interop = new Interop(JSRuntime);
|
||||
if (await interop.FormValid(form))
|
||||
{
|
||||
|
||||
if (!string.IsNullOrEmpty(_title))
|
||||
{
|
||||
if (!Utilities.ValidateEffectiveExpiryDates(_effectivedate, _expirydate))
|
||||
{
|
||||
AddModuleMessage(SharedLocalizer["Message.EffectiveExpiryDateError"], MessageType.Warning);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// update module settings first
|
||||
if (_moduleSettingsType != null)
|
||||
{
|
||||
if (_moduleSettings is ISettingsControl moduleSettingsControl)
|
||||
{
|
||||
// module settings updated using explicit interface
|
||||
await moduleSettingsControl.UpdateSettings();
|
||||
}
|
||||
else
|
||||
{
|
||||
// legacy approach - module settings updated by convention (ie. by calling a public method named "UpdateSettings" in settings component)
|
||||
// this method should be removed however the ISettingsControl declaration was not added to the default module template until version 10.1.2
|
||||
_moduleSettings?.GetType().GetMethod("UpdateSettings")?.Invoke(_moduleSettings, null);
|
||||
}
|
||||
}
|
||||
|
||||
// update container settings
|
||||
if (_containerSettingsType != null && _containerSettings is ISettingsControl containerSettingsControl)
|
||||
{
|
||||
await containerSettingsControl.UpdateSettings();
|
||||
}
|
||||
|
||||
// update page module
|
||||
var pagemodule = await PageModuleService.GetPageModuleAsync(ModuleState.PageModuleId);
|
||||
var pageId = pagemodule.PageId; // preserve
|
||||
var pane = pagemodule.Pane; // preserve
|
||||
pagemodule.PageId = int.Parse(_pageId);
|
||||
pagemodule.Title = _title;
|
||||
pagemodule.Pane = _pane;
|
||||
@@ -302,33 +331,21 @@
|
||||
pagemodule.Header = _header;
|
||||
pagemodule.Footer = _footer;
|
||||
await PageModuleService.UpdatePageModuleAsync(pagemodule);
|
||||
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane);
|
||||
|
||||
// update page module order if page or pane changed
|
||||
if (pageId != pagemodule.PageId || pane != pagemodule.Pane)
|
||||
{
|
||||
await PageModuleService.UpdatePageModuleOrderAsync(pageId, pane); // old page/pane
|
||||
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane); // new page/pane
|
||||
}
|
||||
|
||||
// update module
|
||||
var module = await ModuleService.GetModuleAsync(ModuleState.ModuleId);
|
||||
module.AllPages = bool.Parse(_allPages);
|
||||
module.PageModuleId = ModuleState.PageModuleId;
|
||||
module.PermissionList = _permissionGrid.GetPermissionList();
|
||||
await ModuleService.UpdateModuleAsync(module);
|
||||
|
||||
if (_moduleSettingsType != null)
|
||||
{
|
||||
if (_moduleSettings is ISettingsControl moduleSettingsControl)
|
||||
{
|
||||
// module settings updated using explicit interface
|
||||
await moduleSettingsControl.UpdateSettings();
|
||||
}
|
||||
else
|
||||
{
|
||||
// legacy support - module settings updated by convention ( ie. by calling a public method named "UpdateSettings" in settings component )
|
||||
_moduleSettings?.GetType().GetMethod("UpdateSettings")?.Invoke(_moduleSettings, null);
|
||||
}
|
||||
}
|
||||
|
||||
if (_containerSettingsType != null && _containerSettings is ISettingsControl containerSettingsControl)
|
||||
{
|
||||
await containerSettingsControl.UpdateSettings();
|
||||
}
|
||||
|
||||
NavigationManager.NavigateTo(PageState.ReturnUrl);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="name" HelpText="Enter the page name" ResourceKey="Name">Name: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="name" class="form-control" @bind="@_name" maxlength="50" required />
|
||||
<input id="name" class="form-control" @bind="@_name" maxlength="100" required />
|
||||
</div>
|
||||
</div>
|
||||
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||
@@ -81,21 +81,9 @@
|
||||
</div>
|
||||
}
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="navigation" HelpText="Select whether the page is part of the site navigation or hidden" ResourceKey="Navigation">Navigation? </Label>
|
||||
<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">
|
||||
<select id="navigation" class="form-select" @bind="@_isnavigation" required>
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="clickable" HelpText="Select whether the link in the site navigation is enabled or disabled" ResourceKey="Clickable">Clickable? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="clickable" class="form-select" @bind="@_isclickable" required>
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
<input id="title" class="form-control" @bind="@_title" maxlength="200" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
@@ -110,27 +98,6 @@
|
||||
<input id="url" class="form-control" @bind="@_url" maxlength="500" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="icon" HelpText="Optionally provide an icon class name for this page which will be displayed in the site navigation" ResourceKey="Icon">Icon: </Label>
|
||||
<div class="col-sm-8">
|
||||
<InputList Value="@_icon" ValueChanged="IconChanged" DataList="@_icons" ResourceKey="Icon" ResourceType="@_iconresources" />
|
||||
</div>
|
||||
<div class="col-sm-1">
|
||||
<i class="@_icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="effectiveDate" HelpText="The date that this page is active" ResourceKey="EffectiveDate">Effective Date: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input type="date" id="effectiveDate" class="form-control" @bind="@_effectivedate" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="expiryDate" HelpText="The date that this page expires" ResourceKey="ExpiryDate">Expiry Date: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input type="date" id="expiryDate" class="form-control" @bind="@_expirydate" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="personalizable" HelpText="Select whether you would like users to be able to personalize this page with their own content" ResourceKey="Personalizable">Personalizable? </Label>
|
||||
<div class="col-sm-9">
|
||||
@@ -141,15 +108,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Section Name="Appearance" ResourceKey="Appearance" Heading=@Localizer["Appearance.Name"]>
|
||||
<Section Name="Theme" Heading="Theme" ResourceKey="Theme">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="title" HelpText="Optionally enter the page title. If you do not provide a page title, the page name will be used." ResourceKey="Title">Title: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="title" class="form-control" @bind="@_title" maxlength="200" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="theme" HelpText="Select the theme for this page" ResourceKey="Theme">Theme: </Label>
|
||||
<div class="col-sm-9">
|
||||
@@ -181,6 +141,49 @@
|
||||
</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="icon" HelpText="Optionally provide an icon class name for this page which will be displayed in the site navigation" ResourceKey="Icon">Icon: </Label>
|
||||
<div class="col-sm-8">
|
||||
<InputList Value="@_icon" ValueChanged="IconChanged" DataList="@_icons" ResourceKey="Icon" ResourceType="@_iconresources" />
|
||||
</div>
|
||||
<div class="col-sm-1">
|
||||
<i class="@_icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="navigation" HelpText="Select whether the page is part of the site navigation or hidden" ResourceKey="Navigation">Navigation? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="navigation" class="form-select" @bind="@_isnavigation" required>
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="clickable" HelpText="Select whether the link in the site navigation is enabled or disabled" ResourceKey="Clickable">Clickable? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="clickable" class="form-select" @bind="@_isclickable" required>
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="effectiveDate" HelpText="The date that this page is active" ResourceKey="EffectiveDate">Effective Date: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input type="date" id="effectiveDate" class="form-control" @bind="@_effectivedate" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="expiryDate" HelpText="The date that this page expires" ResourceKey="ExpiryDate">Expiry Date: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input type="date" id="expiryDate" class="form-control" @bind="@_expirydate" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
<Section Name="PageContent" ResourceKey="PageContent" Heading=@Localizer["PageContent.Heading"]>
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="name" HelpText="Enter the page name" ResourceKey="Name">Name: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="name" class="form-control" @bind="@_name" maxlength="50" required />
|
||||
<input id="name" class="form-control" @bind="@_name" maxlength="100" required />
|
||||
</div>
|
||||
</div>
|
||||
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||
@@ -46,7 +46,7 @@
|
||||
<Label Class="col-sm-3" For="move" HelpText="Select the location where you would like the page to be moved in relation to other pages" ResourceKey="Move">Move: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="move" class="form-select" @bind="@_insert" required>
|
||||
@if (_parentid == _currentparentid)
|
||||
@if (_parentid == _currentparentid && !_copy)
|
||||
{
|
||||
<option value="="><@Localizer["ThisLocation.Keep"]></option>
|
||||
}
|
||||
@@ -98,21 +98,9 @@
|
||||
</div>
|
||||
}
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="navigation" HelpText="Select whether the page is part of the site navigation or hidden" ResourceKey="Navigation">Navigation? </Label>
|
||||
<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">
|
||||
<select id="navigation" class="form-select" @bind="@_isnavigation" required>
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="clickable" HelpText="Select whether the link in the site navigation is enabled or disabled" ResourceKey="Clickable">Clickable? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="clickable" class="form-select" @bind="@_isclickable" required>
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
<input id="title" class="form-control" @bind="@_title" maxlength="200" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
@@ -127,27 +115,6 @@
|
||||
<input id="url" class="form-control" @bind="@_url" maxlength="500" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="icon" HelpText="Optionally provide an icon class name for this page which will be displayed in the site navigation" ResourceKey="Icon">Icon: </Label>
|
||||
<div class="col-sm-8">
|
||||
<InputList Value="@_icon" ValueChanged="IconChanged" DataList="@_icons" ResourceKey="Icon" ResourceType="@_iconresources" />
|
||||
</div>
|
||||
<div class="col-sm-1">
|
||||
<i class="@_icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="effectiveDate" HelpText="The date that this page is active" ResourceKey="EffectiveDate">Effective Date: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input type="date" id="effectiveDate" class="form-control" @bind="@_effectivedate" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="expiryDate" HelpText="The date that this page expires" ResourceKey="ExpiryDate">Expiry Date: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input type="date" id="expiryDate" class="form-control" @bind="@_expirydate" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="personalizable" HelpText="Select whether you would like users to be able to personalize this page with their own content" ResourceKey="Personalizable">Personalizable? </Label>
|
||||
<div class="col-sm-9">
|
||||
@@ -158,14 +125,8 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Section Name="Appearance" ResourceKey="Appearance" Heading="Appearance">
|
||||
<Section Name="Theme" ResourceKey="Theme" Heading="Theme">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="title" HelpText="Optionally enter the page title. If you do not provide a page title, the page name will be used." ResourceKey="Title">Title: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="title" class="form-control" @bind="@_title" maxlength="200" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="theme" HelpText="Select the theme for this page" ResourceKey="Theme">Theme: </Label>
|
||||
<div class="col-sm-9">
|
||||
@@ -200,6 +161,49 @@
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
<Section Name="Appearance" ResourceKey="Appearance" Heading="Appearance">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="icon" HelpText="Optionally provide an icon class name for this page which will be displayed in the site navigation" ResourceKey="Icon">Icon: </Label>
|
||||
<div class="col-sm-8">
|
||||
<InputList Value="@_icon" ValueChanged="IconChanged" DataList="@_icons" ResourceKey="Icon" ResourceType="@_iconresources" />
|
||||
</div>
|
||||
<div class="col-sm-1">
|
||||
<i class="@_icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="navigation" HelpText="Select whether the page is part of the site navigation or hidden" ResourceKey="Navigation">Navigation? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="navigation" class="form-select" @bind="@_isnavigation" required>
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="clickable" HelpText="Select whether the link in the site navigation is enabled or disabled" ResourceKey="Clickable">Clickable? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="clickable" class="form-select" @bind="@_isclickable" required>
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="effectiveDate" HelpText="The date that this page is active" ResourceKey="EffectiveDate">Effective Date: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input type="date" id="effectiveDate" class="form-control" @bind="@_effectivedate" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="expiryDate" HelpText="The date that this page expires" ResourceKey="ExpiryDate">Expiry Date: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input type="date" id="expiryDate" class="form-control" @bind="@_expirydate" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
<Section Name="PageContent" Heading="Page Content" ResourceKey="PageContent">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
@@ -221,7 +225,10 @@
|
||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||
<br />
|
||||
<br />
|
||||
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon" DeletedBy="@_deletedby" DeletedOn="@_deletedon"></AuditInfo>
|
||||
@if (!_copy)
|
||||
{
|
||||
<AuditInfo CreatedBy="@_createdby" CreatedOn="@_createdon" ModifiedBy="@_modifiedby" ModifiedOn="@_modifiedon" DeletedBy="@_deletedby" DeletedOn="@_deletedon"></AuditInfo>
|
||||
}
|
||||
</TabPanel>
|
||||
<TabPanel Name="Permissions" ResourceKey="Permissions" Heading="Permissions">
|
||||
<div class="container">
|
||||
@@ -237,36 +244,40 @@
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button>
|
||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||
</div>
|
||||
</TabPanel>
|
||||
<TabPanel Name="PageModules" Heading="Modules" ResourceKey="PageModules">
|
||||
<Pager Items="_pageModules">
|
||||
<Header>
|
||||
@if (!_copy)
|
||||
{
|
||||
<TabPanel Name="PageModules" Heading="Modules" ResourceKey="PageModules">
|
||||
<Pager Items="_pageModules">
|
||||
<Header>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th>@Localizer["ModuleTitle"]</th>
|
||||
<th>@Localizer["ModuleDefinition"]</th>
|
||||
</Header>
|
||||
<Row>
|
||||
<td><ActionLink Action="Settings" Text="Edit" Path="@_actualpath" ModuleId="@context.ModuleId" Security="SecurityAccessLevel.Edit" PermissionList="@context.PermissionList" ResourceKey="ModuleSettings" /></td>
|
||||
<td><ActionDialog Header="Delete Module" Message="Are You Sure You Wish To Delete This Module?" Action="Delete" Security="SecurityAccessLevel.Edit" PermissionList="@context.PermissionList" Class="btn btn-danger" OnClick="@(async () => await DeleteModule(context))" ResourceKey="DeleteModule" /></td>
|
||||
<td>@context.Title</td>
|
||||
<td>@context.ModuleDefinition?.Name</td>
|
||||
</Row>
|
||||
</Pager>
|
||||
</TabPanel>
|
||||
@if (_themeSettingsType != null)
|
||||
{
|
||||
<TabPanel Name="ThemeSettings" Heading="Theme Settings" ResourceKey="ThemeSettings">
|
||||
@_themeSettingsComponent
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button>
|
||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||
</Header>
|
||||
<Row>
|
||||
<td><ActionLink Action="Settings" Text="Edit" Path="@_actualpath" ModuleId="@context.ModuleId" Security="SecurityAccessLevel.Edit" PermissionList="@context.PermissionList" ResourceKey="ModuleSettings" /></td>
|
||||
<td><ActionDialog Header="Delete Module" Message="Are You Sure You Wish To Delete This Module?" Action="Delete" Security="SecurityAccessLevel.Edit" PermissionList="@context.PermissionList" Class="btn btn-danger" OnClick="@(async () => await DeleteModule(context))" ResourceKey="DeleteModule" /></td>
|
||||
<td>@context.Title</td>
|
||||
<td>@context.ModuleDefinition?.Name</td>
|
||||
</Row>
|
||||
</Pager>
|
||||
</TabPanel>
|
||||
@if (_themeSettingsType != null)
|
||||
{
|
||||
<TabPanel Name="ThemeSettings" Heading="Theme Settings" ResourceKey="ThemeSettings">
|
||||
@_themeSettingsComponent
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="SavePage">@SharedLocalizer["Save"]</button>
|
||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||
</TabPanel>
|
||||
}
|
||||
|
||||
}
|
||||
</TabStrip>
|
||||
}
|
||||
@@ -345,6 +356,7 @@
|
||||
private List<ThemeControl> _containers = new List<ThemeControl>();
|
||||
private List<Page> _pages;
|
||||
private int _pageId;
|
||||
private bool _copy = false;
|
||||
private string _name;
|
||||
private string _currentparentid;
|
||||
private string _parentid = "-1";
|
||||
@@ -390,6 +402,10 @@
|
||||
{
|
||||
_pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
|
||||
_pageId = Int32.Parse(PageState.QueryString["id"]);
|
||||
if (PageState.QueryString.ContainsKey("copy"))
|
||||
{
|
||||
_copy = bool.Parse(PageState.QueryString["copy"]);
|
||||
}
|
||||
_page = await PageService.GetPageAsync(_pageId);
|
||||
_icons = await SystemService.GetIconsAsync();
|
||||
_iconresources = Utilities.GetFullTypeName(typeof(IconResources).AssemblyQualifiedName);
|
||||
@@ -409,7 +425,7 @@
|
||||
_children = new List<Page>();
|
||||
foreach (Page p in _pages.Where(item => (_parentid == "-1" && item.ParentId == null) || (item.ParentId == int.Parse(_parentid, CultureInfo.InvariantCulture))))
|
||||
{
|
||||
if (p.PageId != _pageId && UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
|
||||
if ((p.PageId != _pageId || _copy) && UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, p.PermissionList))
|
||||
{
|
||||
_children.Add(p);
|
||||
}
|
||||
@@ -436,6 +452,12 @@
|
||||
_expirydate = Utilities.UtcAsLocalDate(_page.ExpiryDate);
|
||||
_ispersonalizable = _page.IsPersonalizable.ToString();
|
||||
|
||||
if (_copy)
|
||||
{
|
||||
_insert = ">";
|
||||
_childid = _page.PageId;
|
||||
}
|
||||
|
||||
// appearance
|
||||
_title = _page.Title;
|
||||
_themetype = _page.ThemeType;
|
||||
@@ -466,6 +488,19 @@
|
||||
// permissions
|
||||
_permissions = _page.PermissionList;
|
||||
_updatemodulepermissions = "True";
|
||||
if (_copy)
|
||||
{
|
||||
_permissions = _page.PermissionList.Select(item => new Permission
|
||||
{
|
||||
SiteId = item.SiteId,
|
||||
EntityName = item.EntityName,
|
||||
EntityId = -1,
|
||||
PermissionName = item.PermissionName,
|
||||
RoleName = item.RoleName,
|
||||
UserId = item.UserId,
|
||||
IsAuthorized = item.IsAuthorized,
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
// page modules
|
||||
var modules = await ModuleService.GetModulesAsync(PageState.Site.SiteId);
|
||||
@@ -480,6 +515,13 @@
|
||||
_deletedon = _page.DeletedOn;
|
||||
|
||||
ThemeSettings();
|
||||
|
||||
if (_copy)
|
||||
{
|
||||
_name = "";
|
||||
_path = "";
|
||||
}
|
||||
|
||||
_initialized = true;
|
||||
}
|
||||
else
|
||||
@@ -550,7 +592,7 @@
|
||||
builder.OpenComponent(0, _themeSettingsType);
|
||||
builder.AddAttribute(1, "RenderModeBoundary", RenderModeBoundary);
|
||||
builder.AddComponentReferenceCapture(2, inst => { _themeSettings = Convert.ChangeType(inst, _themeSettingsType); });
|
||||
|
||||
|
||||
builder.CloseComponent();
|
||||
};
|
||||
}
|
||||
@@ -572,10 +614,18 @@
|
||||
await ScrollToPageTop();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(_themetype) && _containertype != "-")
|
||||
{
|
||||
string currentPath = _page.Path;
|
||||
|
||||
if (_copy)
|
||||
{
|
||||
_page = new Page();
|
||||
_page.SiteId = PageState.Site.SiteId;
|
||||
currentPath = "";
|
||||
}
|
||||
|
||||
_page.Name = _name;
|
||||
|
||||
if (_parentid == "-1")
|
||||
@@ -631,6 +681,13 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// update theme settings
|
||||
if (_themeSettingsType != null && _themeSettings is ISettingsControl themeSettingsControl)
|
||||
{
|
||||
await themeSettingsControl.UpdateSettings();
|
||||
}
|
||||
|
||||
// default page properties
|
||||
if (_insert != "=")
|
||||
{
|
||||
Page child;
|
||||
@@ -684,7 +741,21 @@
|
||||
_page.UpdateModulePermissions = bool.Parse(_updatemodulepermissions);
|
||||
}
|
||||
|
||||
_page = await PageService.UpdatePageAsync(_page);
|
||||
if (_copy)
|
||||
{
|
||||
// create page
|
||||
_page = await PageService.AddPageAsync(_page);
|
||||
await PageService.CopyPageAsync(_pageId, _page.PageId, bool.Parse(_updatemodulepermissions));
|
||||
await logger.LogInformation("Page Added {Page}", _page);
|
||||
}
|
||||
else
|
||||
{
|
||||
// update page
|
||||
_page = await PageService.UpdatePageAsync(_page);
|
||||
await logger.LogInformation("Page Saved {Page}", _page);
|
||||
}
|
||||
|
||||
// update page order
|
||||
await PageService.UpdatePageOrderAsync(_page.SiteId, _page.PageId, _page.ParentId);
|
||||
if (_currentparentid == string.Empty)
|
||||
{
|
||||
@@ -695,12 +766,6 @@
|
||||
await PageService.UpdatePageOrderAsync(_page.SiteId, _page.PageId, int.Parse(_currentparentid));
|
||||
}
|
||||
|
||||
if (_themeSettingsType != null && _themeSettings is ISettingsControl themeSettingsControl)
|
||||
{
|
||||
await themeSettingsControl.UpdateSettings();
|
||||
}
|
||||
|
||||
await logger.LogInformation("Page Saved {Page}", _page);
|
||||
if (!string.IsNullOrEmpty(PageState.ReturnUrl))
|
||||
{
|
||||
NavigationManager.NavigateTo(PageState.ReturnUrl, true); // redirect to page being edited and reload
|
||||
|
||||
@@ -3,10 +3,11 @@
|
||||
@inherits ModuleBase
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IUserService UserService
|
||||
@inject IStringLocalizer<Index> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
@inject ISettingService SettingService
|
||||
@inject ITimeZoneService TimeZoneService
|
||||
@inject ILanguageService LanguageService
|
||||
@inject IStringLocalizer<Index> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
|
||||
@if (_initialized)
|
||||
{
|
||||
@@ -71,6 +72,18 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="culture" HelpText="Your preferred language. Note that you will only be able to choose from languages supported on this site." ResourceKey="CultureCode">Language:</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="culture" class="form-select" @bind="@_culturecode">
|
||||
<option value=""><@SharedLocalizer["Not Specified"]></option>
|
||||
@foreach (var language in _languages)
|
||||
{
|
||||
<option value="@language.Code">@language.Name</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<button type="button" class="btn btn-primary" @onclick="Register">@Localizer["Register"]</button>
|
||||
@@ -95,6 +108,8 @@
|
||||
@code {
|
||||
private bool _initialized = false;
|
||||
private List<Models.TimeZone> _timezones;
|
||||
private IEnumerable<Models.Language> _languages;
|
||||
|
||||
private string _passwordrequirements;
|
||||
private string _username = string.Empty;
|
||||
private ElementReference form;
|
||||
@@ -106,6 +121,7 @@
|
||||
private string _email = string.Empty;
|
||||
private string _displayname = string.Empty;
|
||||
private string _timezoneid = string.Empty;
|
||||
private string _culturecode = string.Empty;
|
||||
private bool _userCreated = false;
|
||||
private bool _allowsitelogin = true;
|
||||
|
||||
@@ -113,10 +129,13 @@
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
_timezones = TimeZoneService.GetTimeZones();
|
||||
_languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId);
|
||||
|
||||
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
|
||||
_allowsitelogin = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AllowSiteLogin", "true"));
|
||||
_timezones = TimeZoneService.GetTimeZones();
|
||||
_timezoneid = PageState.Site.TimeZoneId;
|
||||
_culturecode = PageState.Site.CultureCode;
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
@@ -147,6 +166,7 @@
|
||||
Email = _email,
|
||||
DisplayName = (_displayname == string.Empty ? _username : _displayname),
|
||||
TimeZoneId = _timezoneid,
|
||||
CultureCode = _culturecode,
|
||||
PhotoFileId = null
|
||||
};
|
||||
user = await UserService.AddUserAsync(user);
|
||||
|
||||
@@ -11,11 +11,15 @@
|
||||
@inject IThemeService ThemeService
|
||||
@inject ISettingService SettingService
|
||||
@inject ITimeZoneService TimeZoneService
|
||||
@inject ILocalizationService LocalizationService
|
||||
@inject IServiceProvider ServiceProvider
|
||||
@inject IStringLocalizer<Index> Localizer
|
||||
@inject INotificationService NotificationService
|
||||
@inject IJobService JobService
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
@inject IOutputCacheService CacheService
|
||||
@inject ISiteGroupService SiteGroupService
|
||||
@inject ISiteGroupMemberService SiteGroupMemberService
|
||||
|
||||
@if (_initialized)
|
||||
{
|
||||
@@ -28,22 +32,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="homepage" HelpText="Select the home page for the site (to be used if there is no page with a path of '/')" ResourceKey="HomePage">Home Page: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="homepage" class="form-select" @bind="@_homepageid" required>
|
||||
<option value="-"><@SharedLocalizer["Not Specified"]></option>
|
||||
@foreach (Page page in _pages)
|
||||
{
|
||||
if (UserSecurity.ContainsRole(page.PermissionList, PermissionNames.View, RoleNames.Everyone))
|
||||
{
|
||||
<option value="@(page.PageId)">@(new string('-', page.Level * 2))@(page.Name)</option>
|
||||
}
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="timezone" HelpText="Your time zone" ResourceKey="TimeZone">Time Zone:</Label>
|
||||
<Label Class="col-sm-3" For="timezone" HelpText="The default time zone for the site" ResourceKey="TimeZone">Time Zone:</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="timezone" class="form-select" @bind="@_timezoneid">
|
||||
<option value=""><@SharedLocalizer["Not Specified"]></option>
|
||||
@@ -54,6 +43,18 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="culture" HelpText="The default language of the site's content" ResourceKey="Culture">Language:</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="culture" class="form-select" @bind="@_culturecode">
|
||||
<option value=""><@SharedLocalizer["Not Specified"]></option>
|
||||
@foreach (var culture in _cultures)
|
||||
{
|
||||
<option value="@culture.Name">@culture.DisplayName</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
@@ -207,13 +208,6 @@
|
||||
</div>
|
||||
@if (_smtpenabled == "True" && UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<div class="col-sm-3">
|
||||
</div>
|
||||
<div class="col-sm-9">
|
||||
<strong>@Localizer["Smtp.Required.EnableNotificationJob"]</strong><br />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="host" HelpText="Enter the host name of the SMTP server" ResourceKey="Host">Host: </Label>
|
||||
<div class="col-sm-9">
|
||||
@@ -264,15 +258,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="relay" HelpText="Only specify this option if you have properly configured an SMTP Relay Service to route your outgoing mail. This option will send notifications from the user's email rather than from the Email Sender specified below." ResourceKey="SmtpRelay">Relay Configured? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="relay" class="form-select" @bind="@_smtprelay" required>
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -348,57 +333,6 @@
|
||||
</Section>
|
||||
@if (_aliases != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
<Section Name="Aliases" Heading="Aliases" ResourceKey="Aliases">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="aliases" HelpText="The urls for the site. This can include domain names (ie. domain.com), subdomains (ie. sub.domain.com) or virtual folders (ie. domain.com/folder)." ResourceKey="Aliases">Aliases: </Label>
|
||||
<div class="col-sm-9">
|
||||
<button type="button" class="btn btn-primary" @onclick="AddAlias">@SharedLocalizer["Add"]</button>
|
||||
<Pager Items="@_aliases">
|
||||
<Header>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th>@Localizer["AliasName"]</th>
|
||||
<th>@Localizer["AliasDefault"]</th>
|
||||
</Header>
|
||||
<Row>
|
||||
@if (context.AliasId != _aliasid)
|
||||
{
|
||||
<td>
|
||||
@if (_aliasid == -1)
|
||||
{
|
||||
<button type="button" class="btn btn-primary" @onclick="@(() => EditAlias(context))">@SharedLocalizer["Edit"]</button>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
@if (_aliasid == -1)
|
||||
{
|
||||
<ActionDialog Action="Delete" OnClick="@(async () => await DeleteAlias(context))" ResourceKey="DeleteAlias" Class="btn btn-danger" Header="Delete Alias" Message="@string.Format(Localizer["Confirm.Alias.Delete", context.Name])" />
|
||||
}
|
||||
</td>
|
||||
<td>@context.Name</td>
|
||||
<td>@((context.IsDefault) ? SharedLocalizer["Yes"] : SharedLocalizer["No"])</td>
|
||||
}
|
||||
else
|
||||
{
|
||||
<td><button type="button" class="btn btn-success" @onclick="@(async () => await SaveAlias())">@SharedLocalizer["Save"]</button></td>
|
||||
<td><button type="button" class="btn btn-secondary" @onclick="@(async () => await CancelAlias())">@SharedLocalizer["Cancel"]</button></td>
|
||||
<td>
|
||||
<input id="aliasname" class="form-control" @bind="@_aliasname" />
|
||||
</td>
|
||||
<td>
|
||||
<select id="defaultalias" class="form-select" @bind="@_defaultalias" required>
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</td>
|
||||
}
|
||||
</Row>
|
||||
</Pager>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
<Section Name="Hosting" Heading="Hosting Model" ResourceKey="Hosting">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
@@ -411,8 +345,20 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@if (_rendermode == RenderModes.Static)
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="enhancednavigation" HelpText="Indicates if enhanced navigation should be used with static rendering" ResourceKey="EnhancedNavigation">Enhanced Navigation: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="enhancednavigation" class="form-select" @bind="@_enhancednavigation" required>
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="runtime" HelpText="The render mode for UI components which require interactivity" ResourceKey="Runtime">Interactivity: </Label>
|
||||
<Label Class="col-sm-3" For="runtime" HelpText="The hosting model for UI components which require interactivity" ResourceKey="Runtime">Interactivity: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="runtime" class="form-select" @bind="@_runtime" required>
|
||||
<option value="@Runtimes.Server">@(SharedLocalizer["Runtime" + @Runtimes.Server])</option>
|
||||
@@ -441,6 +387,180 @@
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
<Section Name="Aliases" Heading="Urls" ResourceKey="Aliases">
|
||||
<div class="container">
|
||||
@if (!_addAlias)
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="aliases" HelpText="The urls for this site. This can include domain names (ie. domain.com), subdomains (ie. sub.domain.com) or virtual folders (ie. domain.com/folder)." ResourceKey="Aliases">Urls: </Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<select id="aliases" class="form-select" value="@_aliasid" @onchange="(e => AliasChanged(e))">
|
||||
<option value="-1"><@SharedLocalizer["Not Specified"]></option>
|
||||
@foreach (var alias in _aliases)
|
||||
{
|
||||
<option value="@alias.AliasId">@alias.Name @((alias.IsDefault) ? "(" + Localizer["Default"] + ")" : "")</option>
|
||||
}
|
||||
</select>
|
||||
@if (!_addAlias)
|
||||
{
|
||||
<button type="button" class="btn btn-primary" @onclick="AddAlias">@SharedLocalizer["Add"]</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (_aliasid != -1 || _addAlias)
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="aliasname" HelpText="A url for this site. This can include domain names (ie. domain.com), subdomains (ie. sub.domain.com) or virtual folders (ie. domain.com/folder)." ResourceKey="AliasName">Url: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="aliasname" class="form-control" @bind="@_aliasname" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="defaultalias" HelpText="The default alias for the site. Requests for non-default aliases will be redirected to the default alias." ResourceKey="DefaultAlias">Default? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="defaultalias" class="form-select" @bind="@_defaultalias">
|
||||
<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="col-sm-3"></div>
|
||||
<div class="col-sm-9">
|
||||
@if (_aliasid != -1 || _addAlias)
|
||||
{
|
||||
<button type="button" class="btn btn-success me-2" @onclick="SaveAlias">@SharedLocalizer["Save"]</button>
|
||||
}
|
||||
@if (_aliasid != -1 && !_addAlias)
|
||||
{
|
||||
<ActionDialog Action="Delete" OnClick="@(async () => await DeleteAlias())" ResourceKey="DeleteAlias" Class="btn btn-danger" Header="Delete Alias" Message="@string.Format(Localizer["Confirm.Alias.Delete", _aliasname])" />
|
||||
}
|
||||
@if (_addAlias)
|
||||
{
|
||||
<button type="button" class="btn btn-secondary" @onclick="CancelAlias">@SharedLocalizer["Cancel"]</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
<Section Name="SiteGroupMembers" Heading="Site Groups" ResourceKey="SiteGroupMembers">
|
||||
<div class="container">
|
||||
@if (!_addSiteGroup)
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="group" HelpText="The site groups in this tenant (database)" ResourceKey="SiteGroupMembers">Group: </Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<select id="group" class="form-select" value="@_siteGroupId" @onchange="(e => SiteGroupChanged(e))">
|
||||
<option value="-1"><@SharedLocalizer["Not Specified"]></option>
|
||||
@foreach (var siteGroup in _siteGroups)
|
||||
{
|
||||
<option value="@siteGroup.SiteGroupId">@siteGroup.Name</option>
|
||||
}
|
||||
</select>
|
||||
@if (!_addSiteGroup)
|
||||
{
|
||||
<button type="button" class="btn btn-primary" @onclick="AddSiteGroup">@SharedLocalizer["Add"]</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (_siteGroupId != -1 || _addSiteGroup)
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="groupname" HelpText="Name of the site group" ResourceKey="GroupName">Name: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="groupname" class="form-control" @bind="@_groupName" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="grouptype" HelpText="Defines the specific behavior of the site group" ResourceKey="GroupType">Type: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="grouptype" class="form-select" @bind="@_groupType">
|
||||
<option value="@SiteGroupTypes.Synchronization">@Localizer[@SiteGroupTypes.Synchronization]</option>
|
||||
<option value="@SiteGroupTypes.ChangeDetection">@Localizer[@SiteGroupTypes.ChangeDetection]</option>
|
||||
<option value="@SiteGroupTypes.Localization">@Localizer[SiteGroupTypes.Localization]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (_siteGroupId != -1)
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="site" HelpText="The sites which are members of this site group" ResourceKey="Site">Members: </Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<select id="site" class="form-select" value="@_siteId" @onchange="(e => SiteChanged(e))">
|
||||
<option value="-1"><@SharedLocalizer["Not Specified"]></option>
|
||||
@foreach (var site in _sites)
|
||||
{
|
||||
<option value="@site.SiteId">@site.Name</option>
|
||||
}
|
||||
</select>
|
||||
@if (!_addSiteGroupMember)
|
||||
{
|
||||
<button type="button" class="btn btn-primary" @onclick="AddSiteGroupMember">@SharedLocalizer["Add"]</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button type="button" class="btn btn-primary" @onclick="AddSiteGroupMember">@SharedLocalizer["Select"]</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (_siteGroupId != -1 && _siteId != -1)
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="primary" HelpText="Indicates if the selected member is the primary site of the site group" ResourceKey="Primary">Primary? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="primary" class="form-select" @bind="@_primary">
|
||||
<option value="False">@SharedLocalizer["No"]</option>
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@if (_primary == "False" && !string.IsNullOrEmpty(_synchronized) && (_groupType == SiteGroupTypes.Synchronization || _groupType == SiteGroupTypes.ChangeDetection))
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="synchronized" HelpText="The date/time when the site was last synchronized" ResourceKey="Synchronized">Synchronized: </Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<input id="synchronized" class="form-control" @bind="@_synchronized" disabled />
|
||||
@if (!string.IsNullOrEmpty(_synchronized))
|
||||
{
|
||||
<button type="button" class="btn btn-primary" @onclick="ResetSiteGroupMember">@SharedLocalizer["Reset"]</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
<div class="row mb-1 align-items-center">
|
||||
<div class="col-sm-3"></div>
|
||||
<div class="col-sm-9">
|
||||
@if ((_siteGroupId != -1 || _addSiteGroup))
|
||||
{
|
||||
<button type="button" class="btn btn-success me-2" @onclick="SaveSiteGroupMember">@SharedLocalizer["Save"]</button>
|
||||
}
|
||||
@if (_siteGroupId != -1 && !_addSiteGroup && _siteId != -1 && !_addSiteGroupMember)
|
||||
{
|
||||
<ActionDialog Action="Delete" OnClick="@(async () => await DeleteSiteGroupMember())" ResourceKey="DeleteSiteGroupMember" Class="btn btn-danger" Header="Delete Site Group" Message="@Localizer["Confirm.SiteGroupMember.Delete"]" />
|
||||
}
|
||||
@if (_addSiteGroup)
|
||||
{
|
||||
<button type="button" class="btn btn-secondary" @onclick="CancelSiteGroupMember">@SharedLocalizer["Cancel"]</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
<Section Name="TenantInformation" Heading="Database" ResourceKey="TenantInformation">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
@@ -481,10 +601,11 @@
|
||||
private List<ThemeControl> _containers = new List<ThemeControl>();
|
||||
private List<Page> _pages;
|
||||
private List<Models.TimeZone> _timezones;
|
||||
private IEnumerable<Models.Culture> _cultures;
|
||||
|
||||
private string _name = string.Empty;
|
||||
private string _homepageid = "-";
|
||||
private string _timezoneid = string.Empty;
|
||||
private string _culturecode = string.Empty;
|
||||
private string _isdeleted;
|
||||
private string _sitemap = "";
|
||||
private string _siteguid = "";
|
||||
@@ -522,7 +643,6 @@
|
||||
private string _togglesmtpclientsecret = string.Empty;
|
||||
private string _smtpscopes = string.Empty;
|
||||
private string _smtpsender = string.Empty;
|
||||
private string _smtprelay = "False";
|
||||
private int _retention = 30;
|
||||
|
||||
private string _pwaisenabled;
|
||||
@@ -531,15 +651,28 @@
|
||||
private int _pwasplashiconfileid = -1;
|
||||
private FileManager _pwasplashiconfilemanager;
|
||||
|
||||
private string _rendermode = RenderModes.Interactive;
|
||||
private string _enhancednavigation = "True";
|
||||
private string _runtime = Runtimes.Server;
|
||||
private string _prerender = "True";
|
||||
private string _hybrid = "False";
|
||||
|
||||
private List<Alias> _aliases;
|
||||
private int _aliasid = -1;
|
||||
private string _aliasname;
|
||||
private string _defaultalias;
|
||||
private bool _addAlias = false;
|
||||
|
||||
private string _rendermode = RenderModes.Interactive;
|
||||
private string _runtime = Runtimes.Server;
|
||||
private string _prerender = "True";
|
||||
private string _hybrid = "False";
|
||||
private List<SiteGroup> _siteGroups = new List<SiteGroup>();
|
||||
private List<Site> _sites = new List<Site>();
|
||||
private int _siteGroupId = -1;
|
||||
private int _siteId;
|
||||
private string _groupName = string.Empty;
|
||||
private string _groupType = SiteGroupTypes.Synchronization;
|
||||
private string _primary = "True";
|
||||
private string _synchronized = string.Empty;
|
||||
private bool _addSiteGroup = false;
|
||||
private bool _addSiteGroupMember = false;
|
||||
|
||||
private string _tenant = string.Empty;
|
||||
private string _database = string.Empty;
|
||||
@@ -567,16 +700,14 @@
|
||||
if (site != null)
|
||||
{
|
||||
_timezones = TimeZoneService.GetTimeZones();
|
||||
_cultures = await LocalizationService.GetNeutralCulturesAsync();
|
||||
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
|
||||
|
||||
_pages = await PageService.GetPagesAsync(PageState.Site.SiteId);
|
||||
|
||||
_name = site.Name;
|
||||
_timezoneid = site.TimeZoneId;
|
||||
if (site.HomePageId != null)
|
||||
{
|
||||
_homepageid = site.HomePageId.Value.ToString();
|
||||
}
|
||||
_culturecode = site.CultureCode;
|
||||
_isdeleted = site.IsDeleted.ToString();
|
||||
_sitemap = PageState.Alias.Protocol + PageState.Alias.Name + "/sitemap.xml";
|
||||
_siteguid = site.SiteGuid;
|
||||
@@ -640,7 +771,6 @@
|
||||
_togglesmtpclientsecret = SharedLocalizer["ShowPassword"];
|
||||
_smtpscopes = SettingService.GetSetting(settings, "SMTPScopes", string.Empty);
|
||||
_smtpsender = SettingService.GetSetting(settings, "SMTPSender", string.Empty);
|
||||
_smtprelay = SettingService.GetSetting(settings, "SMTPRelay", "False");
|
||||
_retention = int.Parse(SettingService.GetSetting(settings, "NotificationRetention", "30"));
|
||||
}
|
||||
|
||||
@@ -656,10 +786,11 @@
|
||||
}
|
||||
|
||||
// aliases
|
||||
await GetAliases();
|
||||
await LoadAliases();
|
||||
|
||||
// hosting model
|
||||
_rendermode = site.RenderMode;
|
||||
_enhancednavigation = site.EnhancedNavigation.ToString();
|
||||
_runtime = site.Runtime;
|
||||
_prerender = site.Prerender.ToString();
|
||||
_hybrid = site.Hybrid.ToString();
|
||||
@@ -669,7 +800,7 @@
|
||||
{
|
||||
var tenants = await TenantService.GetTenantsAsync();
|
||||
var _databases = await DatabaseService.GetDatabasesAsync();
|
||||
var tenant = tenants.Find(item => item.TenantId == site.TenantId);
|
||||
var tenant = tenants.Find(item => item.TenantId == PageState.Alias.TenantId);
|
||||
if (tenant != null)
|
||||
{
|
||||
_tenant = tenant.Name;
|
||||
@@ -679,6 +810,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
// site groups
|
||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
await LoadSiteGroups();
|
||||
}
|
||||
|
||||
// audit
|
||||
_createdby = site.CreatedBy;
|
||||
_createdon = site.CreatedOn;
|
||||
@@ -746,7 +883,7 @@
|
||||
{
|
||||
site.Name = _name;
|
||||
site.TimeZoneId = _timezoneid;
|
||||
site.HomePageId = (_homepageid != "-" ? int.Parse(_homepageid) : null);
|
||||
site.CultureCode = _culturecode;
|
||||
site.IsDeleted = (_isdeleted == null ? true : Boolean.Parse(_isdeleted));
|
||||
|
||||
// appearance
|
||||
@@ -807,13 +944,11 @@
|
||||
// hosting model
|
||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
if (site.RenderMode != _rendermode || site.Runtime != _runtime || site.Prerender != bool.Parse(_prerender) || site.Hybrid != bool.Parse(_hybrid))
|
||||
{
|
||||
site.RenderMode = _rendermode;
|
||||
site.Runtime = _runtime;
|
||||
site.Prerender = bool.Parse(_prerender);
|
||||
site.Hybrid = bool.Parse(_hybrid);
|
||||
}
|
||||
site.RenderMode = _rendermode;
|
||||
site.EnhancedNavigation = bool.Parse(_enhancednavigation);
|
||||
site.Runtime = _runtime;
|
||||
site.Prerender = bool.Parse(_prerender);
|
||||
site.Hybrid = bool.Parse(_hybrid);
|
||||
}
|
||||
|
||||
site = await SiteService.UpdateSiteAsync(site);
|
||||
@@ -834,8 +969,18 @@
|
||||
settings = SettingService.SetSetting(settings, "SMTPClientSecret", _smtpclientsecret, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPScopes", _smtpscopes, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPSender", _smtpsender, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPRelay", _smtprelay, true);
|
||||
settings = SettingService.SetSetting(settings, "NotificationRetention", _retention.ToString(), true);
|
||||
|
||||
if (_smtpenabled == "True")
|
||||
{
|
||||
var jobs = await JobService.GetJobsAsync();
|
||||
var job = jobs.FirstOrDefault(item => item.JobType == "Oqtane.Infrastructure.NotificationJob, Oqtane.Server");
|
||||
if (job != null && !job.IsEnabled)
|
||||
{
|
||||
job.IsEnabled = true;
|
||||
await JobService.UpdateJobAsync(job);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//cookie consent
|
||||
@@ -874,17 +1019,17 @@
|
||||
try
|
||||
{
|
||||
var aliases = await AliasService.GetAliasesAsync();
|
||||
if (aliases.Any(item => item.SiteId != PageState.Site.SiteId || item.TenantId != PageState.Site.TenantId))
|
||||
if (aliases.Any(item => item.SiteId != PageState.Site.SiteId || item.TenantId != PageState.Alias.TenantId))
|
||||
{
|
||||
await SiteService.DeleteSiteAsync(PageState.Site.SiteId);
|
||||
await logger.LogInformation("Site Deleted {SiteId}", PageState.Site.SiteId);
|
||||
|
||||
foreach (Alias alias in aliases.Where(item => item.SiteId == PageState.Site.SiteId && item.TenantId == PageState.Site.TenantId))
|
||||
foreach (Alias alias in aliases.Where(item => item.SiteId == PageState.Site.SiteId && item.TenantId == PageState.Alias.TenantId))
|
||||
{
|
||||
await AliasService.DeleteAliasAsync(alias.AliasId);
|
||||
}
|
||||
|
||||
var redirect = aliases.First(item => item.SiteId != PageState.Site.SiteId || item.TenantId != PageState.Site.TenantId);
|
||||
var redirect = aliases.First(item => item.SiteId != PageState.Site.SiteId || item.TenantId != PageState.Alias.TenantId);
|
||||
NavigationManager.NavigateTo(PageState.Uri.Scheme + "://" + redirect.Name, true);
|
||||
}
|
||||
else
|
||||
@@ -945,7 +1090,10 @@
|
||||
{
|
||||
try
|
||||
{
|
||||
_smtpenabled = "True";
|
||||
|
||||
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||
settings = SettingService.SetSetting(settings, "SMTPEnabled", _smtpenabled, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPHost", _smtphost, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPPort", _smtpport, true);
|
||||
settings = SettingService.SetSetting(settings, "SMTPSSL", _smtpssl, true);
|
||||
@@ -960,6 +1108,14 @@
|
||||
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
|
||||
await logger.LogInformation("Site SMTP Settings Saved");
|
||||
|
||||
var jobs = await JobService.GetJobsAsync();
|
||||
var job = jobs.FirstOrDefault(item => item.JobType == "Oqtane.Infrastructure.NotificationJob, Oqtane.Server");
|
||||
if (job != null && !job.IsEnabled)
|
||||
{
|
||||
job.IsEnabled = true;
|
||||
await JobService.UpdateJobAsync(job);
|
||||
}
|
||||
|
||||
await NotificationService.AddNotificationAsync(new Notification(PageState.Site.SiteId, PageState.User, PageState.Site.Name + " SMTP Configuration Test", "SMTP Server Is Configured Correctly."));
|
||||
AddModuleMessage(Localizer["Info.Smtp.SaveSettings"], MessageType.Info);
|
||||
await ScrollToPageTop();
|
||||
@@ -976,95 +1132,100 @@
|
||||
}
|
||||
}
|
||||
|
||||
private async Task GetAliases()
|
||||
private async Task LoadAliases()
|
||||
{
|
||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
_aliases = await AliasService.GetAliasesAsync();
|
||||
_aliases = _aliases.Where(item => item.SiteId == PageState.Site.SiteId && item.TenantId == PageState.Alias.TenantId).OrderBy(item => item.AliasId).ToList();
|
||||
_aliasid = -1;
|
||||
_addAlias = false;
|
||||
}
|
||||
|
||||
private async void AliasChanged(ChangeEventArgs e)
|
||||
{
|
||||
_aliasid = int.Parse(e.Value.ToString());
|
||||
if (_aliasid != -1)
|
||||
{
|
||||
_aliases = await AliasService.GetAliasesAsync();
|
||||
_aliases = _aliases.Where(item => item.SiteId == PageState.Site.SiteId && item.TenantId == PageState.Site.TenantId).OrderBy(item => item.AliasId).ToList();
|
||||
var alias = _aliases.FirstOrDefault(item => item.AliasId == _aliasid);
|
||||
if (alias != null)
|
||||
{
|
||||
_aliasname = alias.Name;
|
||||
_defaultalias = alias.IsDefault.ToString();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_aliasname = "";
|
||||
_defaultalias = "False";
|
||||
}
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private void AddAlias()
|
||||
{
|
||||
_aliases.Add(new Alias { AliasId = 0, Name = "", IsDefault = false });
|
||||
_aliasid = 0;
|
||||
_aliasid = -1;
|
||||
_aliasname = "";
|
||||
_defaultalias = "False";
|
||||
_addAlias = true;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private void EditAlias(Alias alias)
|
||||
private async Task DeleteAlias()
|
||||
{
|
||||
_aliasid = alias.AliasId;
|
||||
_aliasname = alias.Name;
|
||||
_defaultalias = alias.IsDefault.ToString();
|
||||
await AliasService.DeleteAliasAsync(_aliasid);
|
||||
await LoadAliases();
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task DeleteAlias(Alias alias)
|
||||
{
|
||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
await AliasService.DeleteAliasAsync(alias.AliasId);
|
||||
await GetAliases();
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SaveAlias()
|
||||
{
|
||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
if (!string.IsNullOrEmpty(_aliasname))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_aliasname))
|
||||
var aliases = await AliasService.GetAliasesAsync();
|
||||
|
||||
int protocolIndex = _aliasname.IndexOf("://", StringComparison.OrdinalIgnoreCase);
|
||||
if (protocolIndex != -1)
|
||||
{
|
||||
var aliases = await AliasService.GetAliasesAsync();
|
||||
_aliasname = _aliasname.Substring(protocolIndex + 3);
|
||||
}
|
||||
|
||||
int protocolIndex = _aliasname.IndexOf("://", StringComparison.OrdinalIgnoreCase);
|
||||
if (protocolIndex != -1)
|
||||
var alias = aliases.FirstOrDefault(item => item.Name == _aliasname);
|
||||
|
||||
bool unique = (alias == null || alias.AliasId == _aliasid);
|
||||
|
||||
if (unique)
|
||||
{
|
||||
if (_aliasid == -1)
|
||||
{
|
||||
_aliasname = _aliasname.Substring(protocolIndex + 3);
|
||||
alias = new Alias { SiteId = PageState.Site.SiteId, TenantId = PageState.Alias.TenantId, Name = _aliasname, IsDefault = bool.Parse(_defaultalias) };
|
||||
await AliasService.AddAliasAsync(alias);
|
||||
}
|
||||
|
||||
var alias = aliases.FirstOrDefault(item => item.Name == _aliasname);
|
||||
|
||||
bool unique = (alias == null || alias.AliasId == _aliasid);
|
||||
|
||||
if (unique)
|
||||
else
|
||||
{
|
||||
if (_aliasid == 0)
|
||||
alias = _aliases.SingleOrDefault(item => item.AliasId == _aliasid);
|
||||
if (alias != null)
|
||||
{
|
||||
alias = new Alias { SiteId = PageState.Site.SiteId, TenantId = PageState.Site.TenantId, Name = _aliasname, IsDefault = bool.Parse(_defaultalias) };
|
||||
await AliasService.AddAliasAsync(alias);
|
||||
}
|
||||
else
|
||||
{
|
||||
alias = _aliases.SingleOrDefault(item => item.AliasId == _aliasid);
|
||||
if (alias != null)
|
||||
{
|
||||
alias.Name = _aliasname;
|
||||
alias.IsDefault = bool.Parse(_defaultalias);
|
||||
await AliasService.UpdateAliasAsync(alias);
|
||||
}
|
||||
alias.Name = _aliasname;
|
||||
alias.IsDefault = bool.Parse(_defaultalias);
|
||||
await AliasService.UpdateAliasAsync(alias);
|
||||
}
|
||||
}
|
||||
|
||||
await GetAliases();
|
||||
_aliasid = -1;
|
||||
_aliasname = "";
|
||||
StateHasChanged();
|
||||
}
|
||||
else // Duplicate alias
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.Aliases.Taken"], MessageType.Warning);
|
||||
await ScrollToPageTop();
|
||||
}
|
||||
await LoadAliases();
|
||||
_aliasid = -1;
|
||||
_aliasname = "";
|
||||
StateHasChanged();
|
||||
}
|
||||
else // Duplicate alias
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.Aliases.Taken"], MessageType.Warning);
|
||||
await ScrollToPageTop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CancelAlias()
|
||||
{
|
||||
await GetAliases();
|
||||
await LoadAliases();
|
||||
_aliasid = -1;
|
||||
_aliasname = "";
|
||||
StateHasChanged();
|
||||
@@ -1074,4 +1235,205 @@
|
||||
await CacheService.EvictByTag(Constants.SitemapOutputCacheTag);
|
||||
AddModuleMessage(Localizer["Success.SiteMap.CacheEvicted"], MessageType.Success);
|
||||
}
|
||||
|
||||
private async Task LoadSiteGroups()
|
||||
{
|
||||
_siteGroups = await SiteGroupService.GetSiteGroupsAsync();
|
||||
_siteGroupId = -1;
|
||||
_addSiteGroup = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task SiteGroupChanged(ChangeEventArgs e)
|
||||
{
|
||||
_siteGroupId = int.Parse(e.Value.ToString());
|
||||
if (_siteGroupId != -1)
|
||||
{
|
||||
var group = _siteGroups.FirstOrDefault(item => item.SiteGroupId == _siteGroupId);
|
||||
if (group != null)
|
||||
{
|
||||
_groupName = group.Name;
|
||||
_groupType = group.Type;
|
||||
_siteId = -1;
|
||||
_primary = "False";
|
||||
_addSiteGroupMember = false;
|
||||
|
||||
await LoadSites();
|
||||
}
|
||||
}
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task LoadSites()
|
||||
{
|
||||
var siteGroupMembers = await SiteGroupMemberService.GetSiteGroupMembersAsync(-1, _siteGroupId);
|
||||
|
||||
_sites = await SiteService.GetSitesAsync();
|
||||
if (_addSiteGroupMember)
|
||||
{
|
||||
// include sites which are not members
|
||||
_sites = _sites.ExceptBy(siteGroupMembers.Select(item => item.SiteId), item => item.SiteId).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
// include sites which are members
|
||||
_sites = _sites.Where(item => siteGroupMembers.Any(item2 => item2.SiteId == item.SiteId)).ToList();
|
||||
var group = _siteGroups.FirstOrDefault(item => item.SiteGroupId == _siteGroupId);
|
||||
foreach (var site in _sites)
|
||||
{
|
||||
if (group.PrimarySiteId == site.SiteId)
|
||||
{
|
||||
site.Name += $" ({Localizer["Primary"]})";
|
||||
}
|
||||
else
|
||||
{
|
||||
site.Name += $" ({Localizer["Secondary"]})";
|
||||
}
|
||||
}
|
||||
|
||||
var siteGroupMember = siteGroupMembers.FirstOrDefault(item => item.SiteId == _siteId);
|
||||
if (siteGroupMember != null)
|
||||
{
|
||||
_primary = (siteGroupMember.SiteGroup.PrimarySiteId == _siteId) ? "True" : "False";
|
||||
_synchronized = UtcToLocal(siteGroupMember.SynchronizedOn).ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SiteChanged(ChangeEventArgs e)
|
||||
{
|
||||
_siteId = int.Parse(e.Value.ToString());
|
||||
var siteGroupMember = await SiteGroupMemberService.GetSiteGroupMemberAsync(_siteId, _siteGroupId);
|
||||
if (siteGroupMember != null)
|
||||
{
|
||||
_primary = (siteGroupMember.SiteGroup.PrimarySiteId == _siteId) ? "True" : "False";
|
||||
_synchronized = UtcToLocal(siteGroupMember.SynchronizedOn).ToString();
|
||||
}
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task AddSiteGroup()
|
||||
{
|
||||
_groupName = "";
|
||||
_siteId = PageState.Site.SiteId;
|
||||
_primary = "True";
|
||||
_synchronized = "";
|
||||
_addSiteGroup = true;
|
||||
}
|
||||
|
||||
private async Task AddSiteGroupMember()
|
||||
{
|
||||
_addSiteGroupMember = !_addSiteGroupMember;
|
||||
_siteId = -1;
|
||||
await LoadSites();
|
||||
}
|
||||
|
||||
private async Task SaveSiteGroupMember()
|
||||
{
|
||||
if (string.IsNullOrEmpty(_groupName))
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.Required.GroupName"], MessageType.Warning);
|
||||
await ScrollToPageTop();
|
||||
return;
|
||||
}
|
||||
|
||||
SiteGroup siteGroup = null;
|
||||
|
||||
if (_siteGroupId == -1)
|
||||
{
|
||||
siteGroup = new SiteGroup
|
||||
{
|
||||
Name = _groupName,
|
||||
Type = _groupType,
|
||||
PrimarySiteId = _siteId,
|
||||
Synchronize = false
|
||||
};
|
||||
siteGroup = await SiteGroupService.AddSiteGroupAsync(siteGroup);
|
||||
}
|
||||
else
|
||||
{
|
||||
siteGroup = _siteGroups.FirstOrDefault(item => item.SiteGroupId == _siteGroupId);
|
||||
if (siteGroup != null)
|
||||
{
|
||||
siteGroup.Name = _groupName;
|
||||
siteGroup.Type = _groupType;
|
||||
siteGroup.PrimarySiteId = (_primary == "True") ? _siteId : siteGroup.PrimarySiteId;
|
||||
siteGroup = await SiteGroupService.UpdateSiteGroupAsync(siteGroup);
|
||||
}
|
||||
else
|
||||
{
|
||||
siteGroup = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (siteGroup != null)
|
||||
{
|
||||
if (_siteId != -1)
|
||||
{
|
||||
var siteGroupMember = await SiteGroupMemberService.GetSiteGroupMemberAsync(_siteId, siteGroup.SiteGroupId);
|
||||
if (siteGroupMember == null)
|
||||
{
|
||||
siteGroupMember = new SiteGroupMember
|
||||
{
|
||||
SiteGroupId = siteGroup.SiteGroupId,
|
||||
SiteId = _siteId
|
||||
};
|
||||
await SiteGroupMemberService.AddSiteGroupMemberAsync(siteGroupMember);
|
||||
}
|
||||
else
|
||||
{
|
||||
siteGroupMember.SynchronizedOn = string.IsNullOrEmpty(_synchronized) ? null : siteGroupMember.SynchronizedOn;
|
||||
await SiteGroupMemberService.UpdateSiteGroupMemberAsync(siteGroupMember);
|
||||
}
|
||||
}
|
||||
|
||||
if (siteGroup.Type == SiteGroupTypes.Synchronization)
|
||||
{
|
||||
// enable synchronization job if it is not enabled already
|
||||
var jobs = await JobService.GetJobsAsync();
|
||||
var job = jobs.FirstOrDefault(item => item.JobType == "Oqtane.Infrastructure.SynchronizationJob, Oqtane.Server");
|
||||
if (job != null && !job.IsEnabled)
|
||||
{
|
||||
job.IsEnabled = true;
|
||||
await JobService.UpdateJobAsync(job);
|
||||
}
|
||||
}
|
||||
|
||||
await LoadSiteGroups();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CancelSiteGroupMember()
|
||||
{
|
||||
_groupName = "";
|
||||
await LoadSiteGroups();
|
||||
}
|
||||
|
||||
private async Task DeleteSiteGroupMember()
|
||||
{
|
||||
if (_siteGroupId != -1)
|
||||
{
|
||||
if (_siteId != -1)
|
||||
{
|
||||
var siteGroupMember = await SiteGroupMemberService.GetSiteGroupMemberAsync(_siteId, _siteGroupId);
|
||||
if (siteGroupMember != null)
|
||||
{
|
||||
await SiteGroupMemberService.DeleteSiteGroupMemberAsync(siteGroupMember.SiteGroupMemberId);
|
||||
}
|
||||
}
|
||||
|
||||
var siteGroupMembers = await SiteGroupMemberService.GetSiteGroupMembersAsync(-1, _siteGroupId);
|
||||
if (!siteGroupMembers.Any())
|
||||
{
|
||||
await SiteGroupService.DeleteSiteGroupAsync(_siteGroupId);
|
||||
}
|
||||
|
||||
await LoadSiteGroups();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ResetSiteGroupMember()
|
||||
{
|
||||
_synchronized = "";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
@inject ITenantService TenantService
|
||||
@inject IAliasService AliasService
|
||||
@inject ISiteService SiteService
|
||||
@inject IThemeService ThemeService
|
||||
@inject ISiteTemplateService SiteTemplateService
|
||||
@inject IUserService UserService
|
||||
@inject IInstallationService InstallationService
|
||||
@@ -29,33 +28,9 @@ else
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="alias" HelpText="The urls for the site (comman delimited). This can include domain names (ie. domain.com), subdomains (ie. sub.domain.com) or virtual folders (ie. domain.com/folder)." ResourceKey="Aliases">Urls: </Label>
|
||||
<Label Class="col-sm-3" For="alias" HelpText="The primary url for the site. This can be a domain name (ie. domain.com), subdomain (ie. sub.domain.com) or a virtual folder (ie. domain.com/folder)." ResourceKey="Aliases">Url: </Label>
|
||||
<div class="col-sm-9">
|
||||
<textarea id="alias" class="form-control" @bind="@_urls" rows="3" required></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="defaultTheme" HelpText="Select the default theme for the website" ResourceKey="DefaultTheme">Default Theme: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="defaultTheme" class="form-select" value="@_themetype" @onchange="(e => ThemeChanged(e))" required>
|
||||
<option value="-"><@Localizer["Theme.Select"]></option>
|
||||
@foreach (var theme in _themes)
|
||||
{
|
||||
<option value="@theme.TypeName">@theme.Name</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="defaultContainer" HelpText="Select the default container for the site" ResourceKey="DefaultContainer">Default Container: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="defaultContainer" class="form-select" @bind="@_containertype" required>
|
||||
<option value="-"><@Localizer["Container.Select"]></option>
|
||||
@foreach (var container in _containers)
|
||||
{
|
||||
<option value="@container.TypeName">@container.Name</option>
|
||||
}
|
||||
</select>
|
||||
<input id="alias" class="form-control" @bind="@_urls" required></input>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
@@ -70,26 +45,6 @@ else
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="rendermode" HelpText="The default render mode for the site" ResourceKey="Rendermode">Render Mode: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="rendermode" class="form-select" @bind="@_rendermode" required>
|
||||
<option value="@RenderModes.Interactive">@(SharedLocalizer["RenderMode" + @RenderModes.Interactive])</option>
|
||||
<option value="@RenderModes.Static">@(SharedLocalizer["RenderMode" + @RenderModes.Static])</option>
|
||||
<option value="@RenderModes.Headless">@(SharedLocalizer["RenderMode" + @RenderModes.Headless])</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="runtime" HelpText="The render mode for UI components which require interactivity" ResourceKey="Runtime">Interactivity: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="runtime" class="form-select" @bind="@_runtime" required>
|
||||
<option value="@Runtimes.Server">@(SharedLocalizer["Runtime" + @Runtimes.Server])</option>
|
||||
<option value="@Runtimes.WebAssembly">@(SharedLocalizer["Runtime" + @Runtimes.WebAssembly])</option>
|
||||
<option value="@Runtimes.Auto">@(SharedLocalizer["Runtime" + @Runtimes.Auto])</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="tenant" HelpText="Select the database for the site" ResourceKey="Tenant">Database: </Label>
|
||||
<div class="col-sm-9">
|
||||
@@ -186,9 +141,6 @@ else
|
||||
private bool _showConnectionString = false;
|
||||
private string _connectionString = string.Empty;
|
||||
|
||||
private List<Theme> _themeList;
|
||||
private List<ThemeControl> _themes = new List<ThemeControl>();
|
||||
private List<ThemeControl> _containers = new List<ThemeControl>();
|
||||
private List<SiteTemplate> _siteTemplates;
|
||||
private List<Tenant> _tenants;
|
||||
private string _tenantid = "-";
|
||||
@@ -200,11 +152,7 @@ else
|
||||
|
||||
private string _name = string.Empty;
|
||||
private string _urls = string.Empty;
|
||||
private string _themetype = "-";
|
||||
private string _containertype = "-";
|
||||
private string _sitetemplatetype = "-";
|
||||
private string _rendermode = RenderModes.Static;
|
||||
private string _runtime = Runtimes.Server;
|
||||
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||
|
||||
@@ -215,19 +163,11 @@ else
|
||||
{
|
||||
_tenantid = _tenants.First(item => item.Name == TenantNames.Master).TenantId.ToString();
|
||||
}
|
||||
_urls = PageState.Alias.Name;
|
||||
_themeList = await ThemeService.GetThemesAsync(PageState.Site.SiteId);
|
||||
_themes = ThemeService.GetThemeControls(_themeList);
|
||||
if (_themes.Any(item => item.TypeName == Constants.DefaultTheme))
|
||||
{
|
||||
_themetype = Constants.DefaultTheme;
|
||||
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
|
||||
_containertype = _containers.First().TypeName;
|
||||
}
|
||||
_urls = PageState.Alias.Name + "/sitename";
|
||||
_siteTemplates = await SiteTemplateService.GetSiteTemplatesAsync();
|
||||
if (_siteTemplates.Any(item => item.TypeName == Constants.DefaultSiteTemplate))
|
||||
if (_siteTemplates.Any(item => item.TypeName == Constants.EmptySiteTemplate))
|
||||
{
|
||||
_sitetemplatetype = Constants.DefaultSiteTemplate;
|
||||
_sitetemplatetype = Constants.EmptySiteTemplate;
|
||||
}
|
||||
|
||||
_databases = await DatabaseService.GetDatabasesAsync();
|
||||
@@ -281,37 +221,13 @@ else
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async void ThemeChanged(ChangeEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
_themetype = (string)e.Value;
|
||||
if (_themetype != "-")
|
||||
{
|
||||
_containers = ThemeService.GetContainerControls(_themeList, _themetype);
|
||||
_containertype = _containers.First().TypeName;
|
||||
}
|
||||
else
|
||||
{
|
||||
_containers = new List<ThemeControl>();
|
||||
_containertype = "-";
|
||||
}
|
||||
StateHasChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Loading Containers For Theme {ThemeType} {Error}", _themetype, ex.Message);
|
||||
AddModuleMessage(Localizer["Error.Theme.LoadContainers"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SaveSite()
|
||||
{
|
||||
validated = true;
|
||||
var interop = new Interop(JSRuntime);
|
||||
if (await interop.FormValid(form))
|
||||
{
|
||||
if (_tenantid != "-" && _name != string.Empty && _urls != string.Empty && _themetype != "-" && _containertype != "-" && _sitetemplatetype != "-")
|
||||
if (_tenantid != "-" && _name != string.Empty && _urls != string.Empty && _sitetemplatetype != "-")
|
||||
{
|
||||
_urls = Regex.Replace(_urls, @"\r\n?|\n", ",");
|
||||
var duplicates = new List<string>();
|
||||
@@ -399,12 +315,12 @@ else
|
||||
{
|
||||
config.SiteName = _name;
|
||||
config.Aliases = _urls;
|
||||
config.DefaultTheme = _themetype;
|
||||
config.DefaultContainer = _containertype;
|
||||
config.DefaultTheme = Constants.DefaultTheme;
|
||||
config.DefaultContainer = Constants.DefaultContainer;
|
||||
config.DefaultAdminContainer = "";
|
||||
config.SiteTemplate = _sitetemplatetype;
|
||||
config.RenderMode = _rendermode;
|
||||
config.Runtime = _runtime;
|
||||
config.RenderMode = RenderModes.Static;
|
||||
config.Runtime = Runtimes.Server;
|
||||
config.Register = false;
|
||||
|
||||
ShowProgressIndicator();
|
||||
|
||||
@@ -83,14 +83,14 @@ else
|
||||
{
|
||||
@if (_connection != "-")
|
||||
{
|
||||
@if (!string.IsNullOrEmpty(_tenant))
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="databasetype" HelpText="The database type" ResourceKey="DatabaseType">Type: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="databasetype" class="form-control" @bind="@_databasetype" readonly />
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="databasetype" HelpText="The database type" ResourceKey="DatabaseType">Type: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="databasetype" class="form-control" @bind="@_databasetype" readonly />
|
||||
</div>
|
||||
</div>
|
||||
@if (!string.IsNullOrEmpty(_tenant))
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="tenant" HelpText="The database using this connection" ResourceKey="Tenant">Database: </Label>
|
||||
<div class="col-sm-9">
|
||||
@@ -150,130 +150,149 @@ else
|
||||
}
|
||||
|
||||
@code {
|
||||
private string _connection = "-";
|
||||
private Dictionary<string, object> _connections;
|
||||
private List<Tenant> _tenants;
|
||||
private List<Database> _databases;
|
||||
private string _connection = "-";
|
||||
private Dictionary<string, object> _connections;
|
||||
private List<Tenant> _tenants;
|
||||
private List<Database> _databases;
|
||||
|
||||
private string _name = string.Empty;
|
||||
private string _databasetype = string.Empty;
|
||||
private Type _databaseConfigType;
|
||||
private object _databaseConfig;
|
||||
private RenderFragment DatabaseConfigComponent { get; set; }
|
||||
private bool _showConnectionString = false;
|
||||
private string _tenant = string.Empty;
|
||||
private string _connectionstring = string.Empty;
|
||||
private string _connectionstringtype = "password";
|
||||
private string _connectionstringtoggle = string.Empty;
|
||||
private string _sql = string.Empty;
|
||||
private List<Dictionary<string, string>> _results;
|
||||
private string _name = string.Empty;
|
||||
private string _databasetype = string.Empty;
|
||||
private Type _databaseConfigType;
|
||||
private object _databaseConfig;
|
||||
private RenderFragment DatabaseConfigComponent { get; set; }
|
||||
private bool _showConnectionString = false;
|
||||
private string _tenant = string.Empty;
|
||||
private string _connectionstring = string.Empty;
|
||||
private string _connectionstringtype = "password";
|
||||
private string _connectionstringtoggle = string.Empty;
|
||||
private string _sql = string.Empty;
|
||||
private List<Dictionary<string, string>> _results;
|
||||
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Host;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
_connections = await SystemService.GetSystemInfoAsync("connectionstrings");
|
||||
_tenants = await TenantService.GetTenantsAsync();
|
||||
_databases = await DatabaseService.GetDatabasesAsync();
|
||||
_connectionstringtoggle = SharedLocalizer["ShowPassword"];
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Loading Tenants {Error}", ex.Message);
|
||||
AddModuleMessage(ex.Message, MessageType.Error);
|
||||
}
|
||||
}
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
_connections = await SystemService.GetSystemInfoAsync("connectionstrings");
|
||||
_tenants = await TenantService.GetTenantsAsync();
|
||||
_databases = await DatabaseService.GetDatabasesAsync();
|
||||
_connectionstringtoggle = SharedLocalizer["ShowPassword"];
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Loading Tenants {Error}", ex.Message);
|
||||
AddModuleMessage(ex.Message, MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private async void ConnectionChanged(ChangeEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
_connection = (string)e.Value;
|
||||
if (_connection != "-" && _connection != "+")
|
||||
{
|
||||
_connectionstring = _connections[_connection].ToString();
|
||||
_tenant = "";
|
||||
_databasetype = "";
|
||||
var tenant = _tenants.FirstOrDefault(item => item.DBConnectionString == _connection);
|
||||
if (tenant != null)
|
||||
{
|
||||
_tenant = tenant.Name;
|
||||
private async void ConnectionChanged(ChangeEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
_connection = (string)e.Value;
|
||||
if (_connection != "-" && _connection != "+")
|
||||
{
|
||||
_connectionstring = _connections[_connection].ToString();
|
||||
_tenant = "";
|
||||
_databasetype = "";
|
||||
var tenant = _tenants.FirstOrDefault(item => item.DBConnectionString == _connection);
|
||||
if (tenant != null)
|
||||
{
|
||||
_tenant = tenant.Name;
|
||||
// hack - there are 3 providers with SqlServerDatabase DBTypes - so we are choosing the last one in alphabetical order
|
||||
_databasetype = _databases.Where(item => item.DBType == tenant.DBType).OrderBy(item => item.Name).Last()?.Name;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_databases.Exists(item => item.IsDefault))
|
||||
{
|
||||
_databasetype = _databases.Find(item => item.IsDefault).Name;
|
||||
}
|
||||
else
|
||||
{
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_connection.Contains(" ("))
|
||||
{
|
||||
_databasetype = _connection.Substring(_connection.LastIndexOf(" (") + 2).Replace(")", "");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_databases.Exists(item => item.IsDefault))
|
||||
{
|
||||
_databasetype = _databases.Find(item => item.IsDefault).Name;
|
||||
}
|
||||
else
|
||||
{
|
||||
_databasetype = Constants.DefaultDBName;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_databases.Exists(item => item.IsDefault))
|
||||
{
|
||||
_databasetype = _databases.Find(item => item.IsDefault).Name;
|
||||
}
|
||||
else
|
||||
{
|
||||
_databasetype = Constants.DefaultDBName;
|
||||
}
|
||||
_showConnectionString = false;
|
||||
LoadDatabaseConfigComponent();
|
||||
}
|
||||
StateHasChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Loading Connection {Connection} {Error}", _connection, ex.Message);
|
||||
AddModuleMessage(ex.Message, MessageType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
_showConnectionString = false;
|
||||
LoadDatabaseConfigComponent();
|
||||
}
|
||||
StateHasChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Loading Connection {Connection} {Error}", _connection, ex.Message);
|
||||
AddModuleMessage(ex.Message, MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void DatabaseTypeChanged(ChangeEventArgs eventArgs)
|
||||
{
|
||||
try
|
||||
{
|
||||
_databasetype = (string)eventArgs.Value;
|
||||
_showConnectionString = false;
|
||||
LoadDatabaseConfigComponent();
|
||||
}
|
||||
catch
|
||||
{
|
||||
AddModuleMessage(Localizer["Error.Database.LoadConfig"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
private void DatabaseTypeChanged(ChangeEventArgs eventArgs)
|
||||
{
|
||||
try
|
||||
{
|
||||
_databasetype = (string)eventArgs.Value;
|
||||
_showConnectionString = false;
|
||||
LoadDatabaseConfigComponent();
|
||||
}
|
||||
catch
|
||||
{
|
||||
AddModuleMessage(Localizer["Error.Database.LoadConfig"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadDatabaseConfigComponent()
|
||||
{
|
||||
var database = _databases.SingleOrDefault(d => d.Name == _databasetype);
|
||||
if (database != null)
|
||||
{
|
||||
_databaseConfigType = Type.GetType(database.ControlType);
|
||||
DatabaseConfigComponent = builder =>
|
||||
{
|
||||
builder.OpenComponent(0, _databaseConfigType);
|
||||
builder.AddComponentReferenceCapture(1, inst => { _databaseConfig = Convert.ChangeType(inst, _databaseConfigType); });
|
||||
builder.CloseComponent();
|
||||
};
|
||||
}
|
||||
}
|
||||
private void LoadDatabaseConfigComponent()
|
||||
{
|
||||
var database = _databases.SingleOrDefault(d => d.Name == _databasetype);
|
||||
if (database != null)
|
||||
{
|
||||
_databaseConfigType = Type.GetType(database.ControlType);
|
||||
DatabaseConfigComponent = builder =>
|
||||
{
|
||||
builder.OpenComponent(0, _databaseConfigType);
|
||||
builder.AddComponentReferenceCapture(1, inst => { _databaseConfig = Convert.ChangeType(inst, _databaseConfigType); });
|
||||
builder.CloseComponent();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowConnectionString()
|
||||
{
|
||||
if (_databaseConfig is IDatabaseConfigControl databaseConfigControl)
|
||||
{
|
||||
_connectionstring = databaseConfigControl.GetConnectionString();
|
||||
}
|
||||
_showConnectionString = !_showConnectionString;
|
||||
}
|
||||
private void ShowConnectionString()
|
||||
{
|
||||
if (_databaseConfig is IDatabaseConfigControl databaseConfigControl)
|
||||
{
|
||||
_connectionstring = databaseConfigControl.GetConnectionString();
|
||||
}
|
||||
_showConnectionString = !_showConnectionString;
|
||||
}
|
||||
|
||||
private async Task Add()
|
||||
{
|
||||
var connectionstring = _connectionstring;
|
||||
if (!_showConnectionString && _databaseConfig is IDatabaseConfigControl databaseConfigControl)
|
||||
{
|
||||
connectionstring = databaseConfigControl.GetConnectionString();
|
||||
}
|
||||
if (!string.IsNullOrEmpty(_name) && !string.IsNullOrEmpty(connectionstring))
|
||||
{
|
||||
var settings = new Dictionary<string, object>();
|
||||
private async Task Add()
|
||||
{
|
||||
var connectionstring = _connectionstring;
|
||||
if (!_showConnectionString && _databaseConfig is IDatabaseConfigControl databaseConfigControl)
|
||||
{
|
||||
connectionstring = databaseConfigControl.GetConnectionString();
|
||||
}
|
||||
if (!string.IsNullOrEmpty(_name) && !string.IsNullOrEmpty(connectionstring))
|
||||
{
|
||||
_name = _name + " (" + _databasetype +")";
|
||||
var settings = new Dictionary<string, object>();
|
||||
settings.Add($"{SettingKeys.ConnectionStringsSection}:{_name}", connectionstring);
|
||||
await SystemService.UpdateSystemInfoAsync(settings);
|
||||
_connections = await SystemService.GetSystemInfoAsync("connectionstrings");
|
||||
|
||||
@@ -271,7 +271,7 @@
|
||||
}
|
||||
|
||||
var tenants = await TenantService.GetTenantsAsync();
|
||||
_tenant = tenants.Find(item => item.TenantId == PageState.Site.TenantId).Name;
|
||||
_tenant = tenants.Find(item => item.TenantId == PageState.Alias.TenantId).Name;
|
||||
_history = await MigrationHistoryService.GetMigrationHistoryAsync();
|
||||
|
||||
_initialized = true;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
@namespace Oqtane.Modules.Admin.Themes
|
||||
@inherits ModuleBase
|
||||
@using System.Text.RegularExpressions
|
||||
@using System.Reflection
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IThemeService ThemeService
|
||||
@inject IModuleService ModuleService
|
||||
@@ -88,6 +89,16 @@
|
||||
{
|
||||
AddModuleMessage(Localizer["Info.Theme.CreatorIntent"], MessageType.Info);
|
||||
}
|
||||
else
|
||||
{
|
||||
var entryAssemblyName = Assembly.GetEntryAssembly().GetName().Name;
|
||||
if (entryAssemblyName.EndsWith(".Oqtane"))
|
||||
{
|
||||
// Oqtane Application assemblies end with .Server.Oqtane or .Client.Oqtane
|
||||
string[] segments = entryAssemblyName.Split('.');
|
||||
_owner = string.Join(".", segments, 0, segments.Length - 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<br />
|
||||
<Section Name="Information" ResourceKey="Information" Heading="Information">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
@@ -81,6 +82,12 @@
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="fingerprint" HelpText="A unique identifier for the theme's static resources. This value can be changed by clicking the Save option below (ie. cache busting)." ResourceKey="Fingerprint">Fingerprint: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="fingerprint" class="form-control" @bind="@_fingerprint" disabled />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
<br />
|
||||
@@ -117,6 +124,7 @@
|
||||
private string _url = "";
|
||||
private string _contact = "";
|
||||
private string _license = "";
|
||||
private string _fingerprint = "";
|
||||
private List<Permission> _permissions = null;
|
||||
private string _createdby;
|
||||
private DateTime _createdon;
|
||||
@@ -143,6 +151,7 @@
|
||||
_url = theme.Url;
|
||||
_contact = theme.Contact;
|
||||
_license = theme.License;
|
||||
_fingerprint = theme.Fingerprint;
|
||||
_permissions = theme.PermissionList;
|
||||
_createdby = theme.CreatedBy;
|
||||
_createdon = theme.CreatedOn;
|
||||
|
||||
@@ -36,6 +36,7 @@ else
|
||||
<th>@Localizer["Url"]</th>
|
||||
<th>@Localizer["Requests"]</th>
|
||||
<th>@Localizer["Requested"]</th>
|
||||
<th>@Localizer["Referrer"]</th>
|
||||
</Header>
|
||||
<Row>
|
||||
<td><ActionLink Action="Edit" Text="Edit" Parameters="@($"id=" + context.UrlMappingId.ToString())" ResourceKey="Edit" /></td>
|
||||
@@ -49,7 +50,8 @@ else
|
||||
</td>
|
||||
<td>@context.Requests</td>
|
||||
<td>@UtcToLocal(context.RequestedOn)</td>
|
||||
</Row>
|
||||
<td>@context.Referrer</td>
|
||||
</Row>
|
||||
</Pager>
|
||||
</TabPanel>
|
||||
<TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings">
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
@inject IFileService FileService
|
||||
@inject IFolderService FolderService
|
||||
@inject ITimeZoneService TimeZoneService
|
||||
@inject ILanguageService LanguageService
|
||||
@inject IJSRuntime jsRuntime
|
||||
@inject IServiceProvider ServiceProvider
|
||||
@inject IStringLocalizer<Index> Localizer
|
||||
@@ -58,6 +59,18 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="culture" HelpText="Your preferred language. Note that you will only be able to choose from languages supported on this site." ResourceKey="CultureCode">Language:</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="culture" class="form-select" @bind="@_culturecode">
|
||||
<option value=""><@SharedLocalizer["Not Specified"]></option>
|
||||
@foreach (var language in _languages)
|
||||
{
|
||||
<option value="@language.Code">@language.Name</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="@_photofileid.ToString()" HelpText="A photo of yourself" ResourceKey="Photo"></Label>
|
||||
<div class="col-sm-9">
|
||||
@@ -114,9 +127,9 @@
|
||||
}
|
||||
@if (_allowpasskeys)
|
||||
{
|
||||
<Section Name="Passkeys" Heading="Passkeys" ResourceKey="Passkeys">
|
||||
<Section Name="Passkeys" Heading="Passkeys" ResourceKey="Passkeys" Expanded="@((_passkeys.Count > 0).ToString())">
|
||||
<button type="button" class="btn btn-primary" @onclick="AddPasskey">@SharedLocalizer["Add"]</button>
|
||||
@if (_passkeys != null && _passkeys.Count > 0)
|
||||
@if (_passkeys.Count > 0)
|
||||
{
|
||||
<Pager Items="@_passkeys">
|
||||
<Header>
|
||||
@@ -142,15 +155,15 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
<div>@Localizer["Message.Passkeys.None"]</div>
|
||||
<div class="mt-2">@Localizer["Message.Passkeys.None"]</div>
|
||||
}
|
||||
</Section>
|
||||
<br />
|
||||
}
|
||||
@if (_allowexternallogin)
|
||||
{
|
||||
<Section Name="Logins" Heading="Logins" ResourceKey="Logins">
|
||||
@if (_logins != null && _logins.Count > 0)
|
||||
<Section Name="Logins" Heading="Logins" ResourceKey="Logins" Expanded="@((_logins.Count > 0).ToString())">
|
||||
@if (_logins.Count > 0)
|
||||
{
|
||||
<Pager Items="@_logins">
|
||||
<Header>
|
||||
@@ -165,7 +178,7 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
<div>@Localizer["Message.Logins.None"]</div>
|
||||
<div class="mt-2">@Localizer["Message.Logins.None"]</div>
|
||||
}
|
||||
</Section>
|
||||
<br />
|
||||
@@ -448,6 +461,9 @@
|
||||
@code {
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.View;
|
||||
|
||||
private List<Models.TimeZone> _timezones;
|
||||
private IEnumerable<Language> _languages;
|
||||
|
||||
private bool _initialized = false;
|
||||
private bool _allowtwofactor = false;
|
||||
private bool _allowpasskeys = false;
|
||||
@@ -458,8 +474,8 @@
|
||||
private string _displayname = string.Empty;
|
||||
private FileManager _filemanager;
|
||||
private int _folderid = -1;
|
||||
private List<Models.TimeZone> _timezones;
|
||||
private string _timezoneid = string.Empty;
|
||||
private string _culturecode = string.Empty;
|
||||
private int _photofileid = -1;
|
||||
private File _photo = null;
|
||||
private string _imagefiles = string.Empty;
|
||||
@@ -493,12 +509,15 @@
|
||||
|
||||
if (PageState.User != null)
|
||||
{
|
||||
_timezones = TimeZoneService.GetTimeZones();
|
||||
_languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId);
|
||||
|
||||
// identity section
|
||||
_username = PageState.User.Username;
|
||||
_email = PageState.User.Email;
|
||||
_displayname = PageState.User.DisplayName;
|
||||
_timezones = TimeZoneService.GetTimeZones();
|
||||
_timezoneid = PageState.User.TimeZoneId;
|
||||
_culturecode = PageState.User.CultureCode;
|
||||
var folder = await FolderService.GetFolderAsync(ModuleState.SiteId, PageState.User.FolderPath);
|
||||
if (folder != null)
|
||||
{
|
||||
@@ -572,6 +591,7 @@
|
||||
user.Email = _email;
|
||||
user.DisplayName = (_displayname == string.Empty ? _username : _displayname);
|
||||
user.TimeZoneId = _timezoneid;
|
||||
user.CultureCode = _culturecode;
|
||||
user.PhotoFileId = _filemanager.GetFileId();
|
||||
if (user.PhotoFileId == -1)
|
||||
{
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
@inject IProfileService ProfileService
|
||||
@inject ISettingService SettingService
|
||||
@inject ITimeZoneService TimeZoneService
|
||||
@inject ILanguageService LanguageService
|
||||
@inject IStringLocalizer<Add> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
|
||||
@@ -55,6 +56,18 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="culture" HelpText="The user's preferred language. Note that you will only be able to choose from languages supported on this site." ResourceKey="CultureCode">Language:</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="culture" class="form-select" @bind="@_culturecode">
|
||||
<option value=""><@SharedLocalizer["Not Specified"]></option>
|
||||
@foreach (var language in _languages)
|
||||
{
|
||||
<option value="@language.Code">@language.Name</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="notify" HelpText="Indicate if new users should receive an email notification" ResourceKey="Notify">Notify? </Label>
|
||||
<div class="col-sm-9">
|
||||
@@ -129,12 +142,15 @@
|
||||
|
||||
@code {
|
||||
private List<Models.TimeZone> _timezones;
|
||||
private IEnumerable<Models.Language> _languages;
|
||||
|
||||
private bool _initialized = false;
|
||||
private string _username = string.Empty;
|
||||
private string _email = string.Empty;
|
||||
private string _confirmed = "True";
|
||||
private string _displayname = string.Empty;
|
||||
private string _timezoneid = string.Empty;
|
||||
private string _culturecode = string.Empty;
|
||||
private string _notify = "True";
|
||||
private List<Profile> _profiles;
|
||||
private Dictionary<string, string> _settings;
|
||||
@@ -147,6 +163,11 @@
|
||||
try
|
||||
{
|
||||
_timezones = TimeZoneService.GetTimeZones();
|
||||
_languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId);
|
||||
|
||||
_timezoneid = PageState.Site.TimeZoneId;
|
||||
_culturecode = PageState.Site.CultureCode;
|
||||
|
||||
_profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId);
|
||||
foreach (var profile in _profiles)
|
||||
{
|
||||
@@ -157,8 +178,9 @@
|
||||
profile.Options = string.Join(",", options.OrderBy(item => item.Value).Select(kvp => $"{kvp.Key}:{kvp.Value}"));
|
||||
}
|
||||
}
|
||||
|
||||
_settings = new Dictionary<string, string>();
|
||||
_timezoneid = PageState.Site.TimeZoneId;
|
||||
|
||||
_initialized = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -194,6 +216,7 @@
|
||||
user.EmailConfirmed = bool.Parse(_confirmed);
|
||||
user.DisplayName = string.IsNullOrWhiteSpace(_displayname) ? _username : _displayname;
|
||||
user.TimeZoneId = _timezoneid;
|
||||
user.CultureCode = _culturecode;
|
||||
user.PhotoFileId = null;
|
||||
user.SuppressNotification = !bool.Parse(_notify);
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
@inject ISettingService SettingService
|
||||
@inject IFileService FileService
|
||||
@inject ITimeZoneService TimeZoneService
|
||||
@inject ILanguageService LanguageService
|
||||
@inject IServiceProvider ServiceProvider
|
||||
@inject IStringLocalizer<Edit> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
@@ -55,6 +56,18 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="culture" HelpText="The user's preferred language. Note that you will only be able to choose from languages supported on this site." ResourceKey="CultureCode">Language:</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="culture" class="form-select" @bind="@_culturecode">
|
||||
<option value=""><@SharedLocalizer["Not Specified"]></option>
|
||||
@foreach (var language in _languages)
|
||||
{
|
||||
<option value="@language.Code">@language.Name</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
@@ -106,8 +119,8 @@
|
||||
<br /><br />
|
||||
@if (_allowpasskeys)
|
||||
{
|
||||
<Section Name="Passkeys" Heading="Passkeys" ResourceKey="Passkeys">
|
||||
@if (_passkeys != null && _passkeys.Count > 0)
|
||||
<Section Name="Passkeys" Heading="Passkeys" ResourceKey="Passkeys" Expanded="@((_passkeys.Count > 0).ToString())">
|
||||
@if (_passkeys.Count > 0)
|
||||
{
|
||||
<Pager Items="@_passkeys">
|
||||
<Header>
|
||||
@@ -122,15 +135,15 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
<div>@Localizer["Message.Passkeys.None"]</div>
|
||||
<div class="mt-2">@Localizer["Message.Passkeys.None"]</div>
|
||||
}
|
||||
</Section>
|
||||
<br />
|
||||
}
|
||||
@if (_allowexternallogin)
|
||||
{
|
||||
<Section Name="Logins" Heading="Logins" ResourceKey="Logins">
|
||||
@if (_logins != null && _logins.Count > 0)
|
||||
<Section Name="Logins" Heading="Logins" ResourceKey="Logins" Expanded="@((_logins.Count > 0).ToString())">
|
||||
@if (_logins.Count > 0)
|
||||
{
|
||||
<Pager Items="@_logins">
|
||||
<Header>
|
||||
@@ -145,7 +158,7 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
<div>@Localizer["Message.Logins.None"]</div>
|
||||
<div class="mt-2">@Localizer["Message.Logins.None"]</div>
|
||||
}
|
||||
</Section>
|
||||
<br />
|
||||
@@ -211,7 +224,7 @@
|
||||
{
|
||||
<button type="button" class="btn btn-primary ms-1" @onclick="ImpersonateUser">@Localizer["Impersonate"]</button>
|
||||
}
|
||||
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host) && _isdeleted == "True")
|
||||
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host) && _candelete)
|
||||
{
|
||||
<ActionDialog Header="Delete User" Message="Are You Sure You Wish To Permanently Delete This User?" Action="Delete" Security="SecurityAccessLevel.Host" Class="btn btn-danger ms-1" OnClick="@(async () => await DeleteUser())" ResourceKey="DeleteUser" />
|
||||
}
|
||||
@@ -224,17 +237,21 @@
|
||||
private bool _allowpasskeys = false;
|
||||
private bool _allowexternallogin = false;
|
||||
|
||||
private List<Models.TimeZone> _timezones;
|
||||
private IEnumerable<Language> _languages;
|
||||
|
||||
private int _userid;
|
||||
private string _username = string.Empty;
|
||||
private string _email = string.Empty;
|
||||
private string _confirmed = string.Empty;
|
||||
private string _displayname = string.Empty;
|
||||
private List<Models.TimeZone> _timezones;
|
||||
private string _timezoneid = string.Empty;
|
||||
private string _culturecode = string.Empty;
|
||||
private string _isdeleted;
|
||||
private string _lastlogin;
|
||||
private string _lastipaddress;
|
||||
private bool _ishost = false;
|
||||
private bool _candelete = false;
|
||||
|
||||
private string _passwordrequirements;
|
||||
private string _password = string.Empty;
|
||||
@@ -270,13 +287,17 @@
|
||||
var user = await UserService.GetUserAsync(_userid, PageState.Site.SiteId);
|
||||
if (user != null)
|
||||
{
|
||||
_timezones = TimeZoneService.GetTimeZones();
|
||||
_languages = await LanguageService.GetLanguagesAsync(PageState.Site.SiteId);
|
||||
|
||||
_username = user.Username;
|
||||
_email = user.Email;
|
||||
_confirmed = user.EmailConfirmed.ToString();
|
||||
_displayname = user.DisplayName;
|
||||
_timezones = TimeZoneService.GetTimeZones();
|
||||
_timezoneid = PageState.User.TimeZoneId;
|
||||
_culturecode = PageState.User.CultureCode;
|
||||
_isdeleted = user.IsDeleted.ToString();
|
||||
_candelete = user.IsDeleted;
|
||||
_lastlogin = string.Format("{0:MMM dd yyyy HH:mm:ss}", UtcToLocal(user.LastLoginOn));
|
||||
_lastipaddress = user.LastIPAddress;
|
||||
_ishost = UserSecurity.ContainsRole(user.Roles, RoleNames.Host);
|
||||
@@ -344,6 +365,7 @@
|
||||
user.EmailConfirmed = bool.Parse(_confirmed);
|
||||
user.DisplayName = string.IsNullOrWhiteSpace(_displayname) ? _username : _displayname;
|
||||
user.TimeZoneId = _timezoneid;
|
||||
user.CultureCode = _culturecode;
|
||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
user.IsDeleted = (_isdeleted == null ? true : Boolean.Parse(_isdeleted));
|
||||
|
||||
@@ -33,7 +33,7 @@ else
|
||||
</div>
|
||||
<br />
|
||||
|
||||
<Pager Items="@users" RowClass="align-middle" SearchProperties="User.Username,User.Email,User.DisplayName">
|
||||
<Pager Items="@users" CurrentPage="@_page.ToString()" OnPageChange="@((page) => _page = page)" RowClass="align-middle" SearchProperties="User.Username,User.Email,User.DisplayName">
|
||||
<Header>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
@@ -60,9 +60,46 @@ else
|
||||
</Row>
|
||||
</Pager>
|
||||
</TabPanel>
|
||||
<TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings" Security="SecurityAccessLevel.Admin">
|
||||
<TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings" Security="SecurityAccessLevel.Host">
|
||||
<div class="container">
|
||||
<Section Name="User" Heading="User Settings" ResourceKey="UserSettings">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="allowsitelogin" HelpText="Do you want to allow users to sign in using a username and password that is managed locally on this site? Note that you should only disable this option if you have already successfully configured an alternative login method, or else you may lock yourself out of the site." ResourceKey="AllowSiteLogin">Allow Local Login?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="allowsitelogin" class="form-select" @bind="@_allowsitelogin">
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="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">Use 2FA?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="twofactor" class="form-select" @bind="@_twofactor">
|
||||
<option value="false">@Localizer["Disabled"]</option>
|
||||
<option value="true">@Localizer["Optional"]</option>
|
||||
<option value="required">@Localizer["Required"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="loginlink" HelpText="Do you want to allow users to login using a time sensitive link sent by email" ResourceKey="LoginLink">Allow Login Link?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="loginlink" class="form-select" @bind="@_loginlink">
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="passkeys" HelpText="Do you want to allow users to login using passkeys (ie. passwordless authentication using WebAuthn/FIDO2)" ResourceKey="Passkeys">Allow Passkeys?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="passkeys" class="form-select" @bind="@_passkeys">
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="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">
|
||||
@@ -72,466 +109,432 @@ else
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
@if (_allowregistration == "true")
|
||||
{
|
||||
@if (_allowregistration == "true")
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="registerurl" HelpText="Optionally provide a custom registration url" ResourceKey="RegisterUrl">Register Url:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="registerurl" class="form-control" @bind="@_registerurl" />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="profileurl" HelpText="Optionally provide a custom profile url" ResourceKey="ProfileUrl">Profile Url:</Label>
|
||||
<Label Class="col-sm-3" For="registerurl" HelpText="Optionally provide a custom registration url" ResourceKey="RegisterUrl">Register Url:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="profileurl" class="form-control" @bind="@_profileurl" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="requireconfirmedemail" HelpText="Do you want to require registered users to verify their email address before they are allowed to log in?" ResourceKey="RequireConfirmedEmail">Require Verified Email?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="requireconfirmedemail" class="form-select" @bind="@_requireconfirmedemail">
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="passkeys" HelpText="Do you want to allow users to login using passkeys (ie. passwordless authentication using WebAuthn/FIDO2)" ResourceKey="Passkeys">Allow Passkeys?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="passkeys" class="form-select" @bind="@_passkeys">
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="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 Authentication?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="twofactor" class="form-select" @bind="@_twofactor">
|
||||
<option value="false">@Localizer["Disabled"]</option>
|
||||
<option value="true">@Localizer["Optional"]</option>
|
||||
<option value="required">@Localizer["Required"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="cookiename" HelpText="You can choose to use a custom authentication cookie name for each site. However please be aware that if you want to share an authentication cookie between sites on the same domain they need to use a consistent cookie name. Also be aware that changing the authentication cookie name will logout all current users." ResourceKey="CookieName">Cookie Name:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="cookiename" class="form-control" @bind="@_cookiename" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="cookiedomain" HelpText="If you would like to share cookies across subdomains you will need to specify a root domain with a leading dot (ie. '.example.com')" ResourceKey="CookieDomain">Cookie Domain:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="cookiedomain" class="form-control" @bind="@_cookiedomain" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="cookieexpiration" HelpText="You can choose to use a custom authentication cookie expiration timespan for each site (e.g. '08:00:00' for 8 hours). The default is 14 days if not specified." ResourceKey="CookieExpiration">Cookie Expiration Timespan:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="cookieexpiration" class="form-control" @bind="@_cookieexpiration" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="alwaysremember" HelpText="Enabling this option will set a permanent cookie in conjunction with the Cookie Expiration Timespan, which will automatically sign in users the next time they visit the site. By default the site will use session cookies." ResourceKey="AlwaysRemember">Always Remember User?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="alwaysremember" class="form-select" @bind="@_alwaysremember">
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="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>
|
||||
<input id="registerurl" class="form-control" @bind="@_registerurl" />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="requireconfirmedemail" HelpText="Do you want to require registered users to verify their email address before they are allowed to log in?" ResourceKey="RequireConfirmedEmail">Require Verified Email?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="requireconfirmedemail" class="form-select" @bind="@_requireconfirmedemail">
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="profileurl" HelpText="Optionally provide a custom profile url" ResourceKey="ProfileUrl">Profile Url:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="profileurl" class="form-control" @bind="@_profileurl" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="cookiename" HelpText="You can choose to use a custom authentication cookie name for each site. However please be aware that if you want to share an authentication cookie between sites on the same domain they need to use a consistent cookie name. Also be aware that changing the authentication cookie name will logout all current users." ResourceKey="CookieName">Cookie Name:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="cookiename" class="form-control" @bind="@_cookiename" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="cookiedomain" HelpText="If you would like to share cookies across subdomains you will need to specify a root domain with a leading dot (ie. '.example.com')" ResourceKey="CookieDomain">Cookie Domain:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="cookiedomain" class="form-control" @bind="@_cookiedomain" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="cookieexpiration" HelpText="You can choose to use a custom authentication cookie expiration timespan for each site (e.g. '08:00:00' for 8 hours). The default is 14 days if not specified." ResourceKey="CookieExpiration">Cookie Expiration Timespan:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="cookieexpiration" class="form-control" @bind="@_cookieexpiration" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="alwaysremember" HelpText="Enabling this option will set a permanent cookie in conjunction with the Cookie Expiration Timespan, which will automatically sign in users the next time they visit the site. By default the site will use session cookies." ResourceKey="AlwaysRemember">Always Remember User?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="alwaysremember" class="form-select" @bind="@_alwaysremember">
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="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>
|
||||
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
<Section Name="Password" Heading="Password Settings" ResourceKey="PasswordSettings">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="minimumlength" HelpText="The Minimum Length For A Password" ResourceKey="RequiredLength">Minimum Length:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="minimumlength" class="form-control" @bind="@_minimumlength" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="uniquecharacters" HelpText="The Minimum Number Of Unique Characters Which A Password Must Contain" ResourceKey="UniqueCharacters">Unique Characters:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="uniquecharacters" class="form-control" @bind="@_uniquecharacters" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="requiredigit" HelpText="Indicate If Passwords Must Contain A Digit" ResourceKey="RequireDigit">Require Digit?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="requiredigit" class="form-select" @bind="@_requiredigit" required>
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
<Section Name="Password" Heading="Password Settings" ResourceKey="PasswordSettings">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="minimumlength" HelpText="The Minimum Length For A Password" ResourceKey="RequiredLength">Minimum Length:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="minimumlength" class="form-control" @bind="@_minimumlength" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="uniquecharacters" HelpText="The Minimum Number Of Unique Characters Which A Password Must Contain" ResourceKey="UniqueCharacters">Unique Characters:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="uniquecharacters" class="form-control" @bind="@_uniquecharacters" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="requiredigit" HelpText="Indicate If Passwords Must Contain A Digit" ResourceKey="RequireDigit">Require Digit?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="requiredigit" class="form-select" @bind="@_requiredigit" required>
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="requireupper" HelpText="Indicate If Passwords Must Contain An Upper Case Character" ResourceKey="RequireUpper">Require Uppercase?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="requireupper" class="form-select" @bind="@_requireupper" required>
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="requirelower" HelpText="Indicate If Passwords Must Contain A Lower Case Character" ResourceKey="RequireLower">Require Lowercase?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="requirelower" class="form-select" @bind="@_requirelower" required>
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="requirepunctuation" HelpText="Indicate if Passwords Must Contain A Non-alphanumeric Character (ie. Punctuation)" ResourceKey="RequirePunctuation">Require Punctuation?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="requirepunctuation" class="form-select" @bind="@_requirepunctuation" required>
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
<Section Name="Lockout" Heading="Lockout Settings" ResourceKey="LockoutSettings">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="maximum" HelpText="The Maximum Number Of Sign In Attempts Before A User Is Locked Out" ResourceKey="MaximumFailures">Maximum Failures:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="maximum" class="form-control" @bind="@_maximumfailures" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="lockoutduration" HelpText="The Number Of Minutes A User Should Be Locked Out" ResourceKey="LockoutDuration">Lockout Duration:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="lockoutduration" class="form-control" @bind="@_lockoutduration" required />
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
<Section Name="ExternalLogin" Heading="External Login Settings" ResourceKey="ExternalLoginSettings">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="provider" HelpText="Select the external login provider" ResourceKey="Provider">Provider:</Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<select id="provider" class="form-select" value="@_provider" @onchange="(e => ProviderChanged(e))">
|
||||
@foreach (var provider in Shared.ExternalLoginProviders.Providers)
|
||||
{
|
||||
<option value="@provider.Name">@Localizer[provider.Name]</option>
|
||||
}
|
||||
</select>
|
||||
@if (!string.IsNullOrEmpty(_providerurl))
|
||||
{
|
||||
<a href="@_providerurl" class="btn btn-secondary" target="_new">@Localizer["Info"]</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="providertype" HelpText="Select the external login provider type" ResourceKey="ProviderType">Provider Type:</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="providertype" class="form-select" value="@_providertype" @onchange="(e => ProviderTypeChanged(e))">
|
||||
<option value="" selected><@Localizer["Not Specified"]></option>
|
||||
<option value="@AuthenticationProviderTypes.OpenIDConnect">@Localizer["OIDC"]</option>
|
||||
<option value="@AuthenticationProviderTypes.OAuth2">@Localizer["OAuth2"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@if (_providertype != "")
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="requireupper" HelpText="Indicate If Passwords Must Contain An Upper Case Character" ResourceKey="RequireUpper">Require Uppercase?</Label>
|
||||
<Label Class="col-sm-3" For="providername" HelpText="Specify a friendly name for the external login provider which will be displayed on the Login page" ResourceKey="ProviderName">Provider Name:</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="requireupper" class="form-select" @bind="@_requireupper" required>
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
<input id="providername" class="form-control" @bind="@_providername" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (_providertype == AuthenticationProviderTypes.OpenIDConnect)
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="requirelower" HelpText="Indicate If Passwords Must Contain A Lower Case Character" ResourceKey="RequireLower">Require Lowercase?</Label>
|
||||
<Label Class="col-sm-3" For="authority" HelpText="The Authority Url or Issuer Url associated with the OpenID Connect provider" ResourceKey="Authority">Authority:</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="requirelower" class="form-select" @bind="@_requirelower" required>
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
<input id="authority" class="form-control" @bind="@_authority" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="requirepunctuation" HelpText="Indicate if Passwords Must Contain A Non-alphanumeric Character (ie. Punctuation)" ResourceKey="RequirePunctuation">Require Punctuation?</Label>
|
||||
<Label Class="col-sm-3" For="metadataurl" HelpText="The discovery endpoint for obtaining metadata for this provider. Only specify if the OpenID Connect provider does not use the standard approach (ie. /.well-known/openid-configuration)" ResourceKey="MetadataUrl">Metadata Url:</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="requirepunctuation" class="form-select" @bind="@_requirepunctuation" required>
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
<input id="metadataurl" class="form-control" @bind="@_metadataurl" />
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
<Section Name="Lockout" Heading="Lockout Settings" ResourceKey="LockoutSettings">
|
||||
</div>
|
||||
}
|
||||
@if (_providertype == AuthenticationProviderTypes.OAuth2)
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="maximum" HelpText="The Maximum Number Of Sign In Attempts Before A User Is Locked Out" ResourceKey="MaximumFailures">Maximum Failures:</Label>
|
||||
<Label Class="col-sm-3" For="authorizationurl" HelpText="The endpoint for obtaining an Authorization Code" ResourceKey="AuthorizationUrl">Authorization Url:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="maximum" class="form-control" @bind="@_maximumfailures" required />
|
||||
<input id="authorizationurl" class="form-control" @bind="@_authorizationurl" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="lockoutduration" HelpText="The Number Of Minutes A User Should Be Locked Out" ResourceKey="LockoutDuration">Lockout Duration:</Label>
|
||||
<Label Class="col-sm-3" For="tokenurl" HelpText="The endpoint for obtaining an Auth Token" ResourceKey="TokenUrl">Token Url:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="lockoutduration" class="form-control" @bind="@_lockoutduration" required />
|
||||
<input id="tokenurl" class="form-control" @bind="@_tokenurl" />
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
<Section Name="ExternalLogin" Heading="External Login Settings" ResourceKey="ExternalLoginSettings">
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="provider" HelpText="Select the external login provider" ResourceKey="Provider">Provider:</Label>
|
||||
<Label Class="col-sm-3" For="userinfourl" HelpText="The endpoint for obtaining user information. This should be an API or Page Url which contains the users email address." ResourceKey="UserInfoUrl">User Info Url:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="userinfourl" class="form-control" @bind="@_userinfourl" />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (_providertype != "")
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="clientid" HelpText="The Client ID from the provider" ResourceKey="ClientID">Client ID:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="clientid" class="form-control" @bind="@_clientid" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="clientsecret" HelpText="The Client Secret from the provider" ResourceKey="ClientSecret">Client Secret:</Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<select id="provider" class="form-select" value="@_provider" @onchange="(e => ProviderChanged(e))">
|
||||
@foreach (var provider in Shared.ExternalLoginProviders.Providers)
|
||||
{
|
||||
<option value="@provider.Name">@Localizer[provider.Name]</option>
|
||||
}
|
||||
</select>
|
||||
@if (!string.IsNullOrEmpty(_providerurl))
|
||||
{
|
||||
<a href="@_providerurl" class="btn btn-secondary" target="_new">@Localizer["Info"]</a>
|
||||
}
|
||||
<input type="@_clientsecrettype" id="clientsecret" class="form-control" @bind="@_clientsecret" />
|
||||
<button type="button" class="btn btn-secondary" @onclick="@ToggleClientSecret">@_toggleclientsecret</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="providertype" HelpText="Select the external login provider type" ResourceKey="ProviderType">Provider Type:</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="providertype" class="form-select" value="@_providertype" @onchange="(e => ProviderTypeChanged(e))">
|
||||
<option value="" selected><@Localizer["Not Specified"]></option>
|
||||
<option value="@AuthenticationProviderTypes.OpenIDConnect">@Localizer["OIDC"]</option>
|
||||
<option value="@AuthenticationProviderTypes.OAuth2">@Localizer["OAuth2"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@if (_providertype != "")
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="providername" HelpText="Specify a friendly name for the external login provider which will be displayed on the Login page" ResourceKey="ProviderName">Provider Name:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="providername" class="form-control" @bind="@_providername" />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (_providertype == AuthenticationProviderTypes.OpenIDConnect)
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="authority" HelpText="The Authority Url or Issuer Url associated with the OpenID Connect provider" ResourceKey="Authority">Authority:</Label>
|
||||
<Label Class="col-sm-3" For="authresponsetype" HelpText="Specify the authorization response type. The default is Authorization Code which is considered to be the most secure option based on the latest OAuth specification." ResourceKey="AuthResponseType">Authorization Response Type:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="authority" class="form-control" @bind="@_authority" />
|
||||
<select id="authresponsetype" class="form-select" @bind="@_authresponsetype" required>
|
||||
<option value="code">@Localizer["AuthFlow.Code"]</option>
|
||||
<option value="code id_token">@Localizer["AuthFlow.CodeIdToken"]</option>
|
||||
<option value="code id_token token">@Localizer["AuthFlow.CodeIdTokenToken"]</option>
|
||||
<option value="code token">@Localizer["AuthFlow.CodeToken"]</option>
|
||||
<option value="id_token">@Localizer["AuthFlow.IdToken"]</option>
|
||||
<option value="id_token token">@Localizer["AuthFlow.IdTokenToken"]</option>
|
||||
<option value="token">@Localizer["AuthFlow.Token"]</option>
|
||||
<option value="none">@Localizer["AuthFlow.None"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="metadataurl" HelpText="The discovery endpoint for obtaining metadata for this provider. Only specify if the OpenID Connect provider does not use the standard approach (ie. /.well-known/openid-configuration)" ResourceKey="MetadataUrl">Metadata Url:</Label>
|
||||
<Label Class="col-sm-3" For="requirenonce" HelpText="Specify if Nonce validation is required for the ID token (the default is true)" ResourceKey="RequireNonce">Require Nonce?</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="metadataurl" class="form-control" @bind="@_metadataurl" />
|
||||
<select id="requirenonce" class="form-select" @bind="@_requirenonce" required>
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="singlelogout" HelpText="Specify if users should be logged out of both the application and provider (the default is false indicating they will only be logged out of the application)" ResourceKey="SingleLogout">Use Single Logout?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="singlelogout" class="form-select" @bind="@_singlelogout" required>
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (_providertype == AuthenticationProviderTypes.OAuth2)
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="authorizationurl" HelpText="The endpoint for obtaining an Authorization Code" ResourceKey="AuthorizationUrl">Authorization Url:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="authorizationurl" class="form-control" @bind="@_authorizationurl" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="tokenurl" HelpText="The endpoint for obtaining an Auth Token" ResourceKey="TokenUrl">Token Url:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="tokenurl" class="form-control" @bind="@_tokenurl" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="userinfourl" HelpText="The endpoint for obtaining user information. This should be an API or Page Url which contains the users email address." ResourceKey="UserInfoUrl">User Info Url:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="userinfourl" class="form-control" @bind="@_userinfourl" />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (_providertype != "")
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="clientid" HelpText="The Client ID from the provider" ResourceKey="ClientID">Client ID:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="clientid" class="form-control" @bind="@_clientid" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="clientsecret" HelpText="The Client Secret from the provider" ResourceKey="ClientSecret">Client Secret:</Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<input type="@_clientsecrettype" id="clientsecret" class="form-control" @bind="@_clientsecret" />
|
||||
<button type="button" class="btn btn-secondary" @onclick="@ToggleClientSecret">@_toggleclientsecret</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@if (_providertype == AuthenticationProviderTypes.OpenIDConnect)
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="authresponsetype" HelpText="Specify the authorization response type. The default is Authorization Code which is considered to be the most secure option based on the latest OAuth specification." ResourceKey="AuthResponseType">Authorization Response Type:</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="authresponsetype" class="form-select" @bind="@_authresponsetype" required>
|
||||
<option value="code">@Localizer["AuthFlow.Code"]</option>
|
||||
<option value="code id_token">@Localizer["AuthFlow.CodeIdToken"]</option>
|
||||
<option value="code id_token token">@Localizer["AuthFlow.CodeIdTokenToken"]</option>
|
||||
<option value="code token">@Localizer["AuthFlow.CodeToken"]</option>
|
||||
<option value="id_token">@Localizer["AuthFlow.IdToken"]</option>
|
||||
<option value="id_token token">@Localizer["AuthFlow.IdTokenToken"]</option>
|
||||
<option value="token">@Localizer["AuthFlow.Token"]</option>
|
||||
<option value="none">@Localizer["AuthFlow.None"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="requirenonce" HelpText="Specify if Nonce validation is required for the ID token (the default is true)" ResourceKey="RequireNonce">Require Nonce?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="requirenonce" class="form-select" @bind="@_requirenonce" required>
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="singlelogout" HelpText="Specify if users should be logged out of both the application and provider (the default is false indicating they will only be logged out of the application)" ResourceKey="SingleLogout">Use Single Logout?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="singlelogout" class="form-select" @bind="@_singlelogout" required>
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="scopes" HelpText="A list of Scopes to request from the provider (separated by commas). If none are specified, standard Scopes will be used by default." ResourceKey="Scopes">Scopes:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="scopes" class="form-control" @bind="@_scopes" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="parameters" HelpText="Optionally specify any additional parameters as name/value pairs to send to the provider (separated by commas if there are multiple)." ResourceKey="Parameters">Parameters:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="parameters" class="form-control" @bind="@_parameters" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="pkce" HelpText="Indicate if the provider supports Proof Key for Code Exchange (PKCE)" ResourceKey="PKCE">Use PKCE?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="pkce" class="form-select" @bind="@_pkce" required>
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="redirecturl" HelpText="The Redirect Url (or Callback Url) which usually needs to be registered with the provider" ResourceKey="RedirectUrl">Redirect Url:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="redirecturl" class="form-control" @bind="@_redirecturl" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="reviewclaims" HelpText="This option will record the full list of Claims returned by the Provider in the Event Log. It should only be used for testing purposes. External Login will be restricted when this option is enabled." ResourceKey="ReviewClaims">Review Claims?</Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<select id="reviewclaims" class="form-select" @bind="@_reviewclaims" required>
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
@if (_reviewclaims == "true")
|
||||
{
|
||||
<a href="@_externalloginurl" target="_blank" class="btn btn-secondary">@SharedLocalizer["Test"]</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="identifierclaimtype" HelpText="Specify the type name of the unique user identifier claim provided by the provider. The default value is 'sub'." ResourceKey="IdentifierClaimType">Identifier Claim:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="identifierclaimtype" class="form-control" @bind="@_identifierclaimtype" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="nameclaimtype" HelpText="Optionally specify the type name of the user's name claim provided by the provider. The typical value is 'name'." ResourceKey="NameClaimType">Name Claim:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="nameclaimtype" class="form-control" @bind="@_nameclaimtype" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="emailclaimtype" HelpText="Optionally specify the type name of the email address claim provided by the provider. The typical value is 'email'," ResourceKey="EmailClaimType">Email Claim:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="emailclaimtype" class="form-control" @bind="@_emailclaimtype" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="roleclaimtype" HelpText="The name of the roles claim provided by the provider" ResourceKey="RoleClaimType">Roles Claim:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="roleclaimtype" class="form-control" @bind="@_roleclaimtype" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="roleclaimmappings" HelpText="Optionally provide a comma delimited list of role names provided by the identity provider, as well as mappings to your site roles." ResourceKey="RoleClaimMappings">Role Claim Mappings:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="roleclaimmappings" class="form-control" @bind="@_roleclaimmappings" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="synchronizeroles" HelpText="This option will add or remove role assignments so that the site roles exactly match the roles provided by the identity provider" ResourceKey="SynchronizeRoles">Synchronize Roles?</Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<select id="synchronizeroles" class="form-select" @bind="@_synchronizeroles" required>
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="profileclaimtypes" HelpText="A comma delimited list of user profile claims provided by the provider, as well as mappings to your user profile definition. For example if the provider includes a 'given_name' claim and you have a 'FirstName' user profile definition you should specify 'given_name:FirstName'." ResourceKey="ProfileClaimTypes">User Profile Claims:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="profileclaimtypes" class="form-control" @bind="@_profileclaimtypes" />
|
||||
</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">
|
||||
<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">
|
||||
<input id="domainfilter" class="form-control" @bind="@_domainfilter" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="createusers" HelpText="Do you want new users to be created automatically? If you disable this option, users must already be registered on the site in order to sign in with their external login." ResourceKey="CreateUsers">Create New Users?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="createusers" class="form-select" @bind="@_createusers">
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="verifyusers" HelpText="Do you want existing users to perform an additional email verification step to link their external login? If you disable this option, existing users will be linked automatically." ResourceKey="VerifyUsers">Verify Existing Users?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="verifyusers" class="form-select" @bind="@_verifyusers">
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="allowhostrole" HelpText="Indicate if host roles are supported from the identity provider. Please use caution with this option as it allows the host user to administrate every site within your installation." ResourceKey="AllowHostRole">Allow Host Role?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="allowhostrole" class="form-select" @bind="@_allowhostrole" required>
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="allowsitelogin" HelpText="Do you want to allow users to sign in using a username and password that is managed locally on this site? Note that you should only disable this option if you have already sucessfully configured an external login provider, or else you may lock yourself out of the site." ResourceKey="AllowSiteLogin">Allow Local Login?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="allowsitelogin" class="form-select" @bind="@_allowsitelogin">
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</Section>
|
||||
<Section Name="Token" Heading="Token Settings" ResourceKey="TokenSettings">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="jwtsecret" HelpText="If you want to want to provide API access, please specify a secret which will be used to encrypt your tokens. The secret should be 16 characters or more to ensure optimal security. Please note that if you change this secret, all existing tokens will become invalid and will need to be regenerated." ResourceKey="Secret">Secret:</Label>
|
||||
<Label Class="col-sm-3" For="scopes" HelpText="A list of Scopes to request from the provider (separated by commas). If none are specified, standard Scopes will be used by default." ResourceKey="Scopes">Scopes:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="scopes" class="form-control" @bind="@_scopes" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="parameters" HelpText="Optionally specify any additional parameters as name/value pairs to send to the provider (separated by commas if there are multiple)." ResourceKey="Parameters">Parameters:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="parameters" class="form-control" @bind="@_parameters" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="pkce" HelpText="Indicate if the provider supports Proof Key for Code Exchange (PKCE)" ResourceKey="PKCE">Use PKCE?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="pkce" class="form-select" @bind="@_pkce" required>
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="redirecturl" HelpText="The Redirect Url (or Callback Url) which usually needs to be registered with the provider" ResourceKey="RedirectUrl">Redirect Url:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="redirecturl" class="form-control" @bind="@_redirecturl" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="reviewclaims" HelpText="This option will record the full list of Claims returned by the Provider in the Event Log. It should only be used for testing purposes. External Login will be restricted when this option is enabled." ResourceKey="ReviewClaims">Review Claims?</Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<input type="@_secrettype" id="jwtsecret" class="form-control" @bind="@_secret" />
|
||||
<button type="button" class="btn btn-secondary" @onclick="@ToggleSecret">@_togglesecret</button>
|
||||
<select id="reviewclaims" class="form-select" @bind="@_reviewclaims" required>
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
@if (_reviewclaims == "true")
|
||||
{
|
||||
<a href="@_externalloginurl" target="_blank" class="btn btn-secondary">@SharedLocalizer["Test"]</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="issuer" HelpText="Optionally provide the issuer of the token" ResourceKey="Issuer">Issuer:</Label>
|
||||
<Label Class="col-sm-3" For="identifierclaimtype" HelpText="Specify the type name of the unique user identifier claim provided by the provider. The default value is 'sub'." ResourceKey="IdentifierClaimType">Identifier Claim:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="issuer" class="form-control" @bind="@_issuer" />
|
||||
<input id="identifierclaimtype" class="form-control" @bind="@_identifierclaimtype" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="audience" HelpText="Optionally provide the audience for the token" ResourceKey="Audience">Audience:</Label>
|
||||
<Label Class="col-sm-3" For="nameclaimtype" HelpText="Optionally specify the type name of the user's name claim provided by the provider. The typical value is 'name'." ResourceKey="NameClaimType">Name Claim:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="audience" class="form-control" @bind="@_audience" />
|
||||
<input id="nameclaimtype" class="form-control" @bind="@_nameclaimtype" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="lifetime" HelpText="The number of minutes for which a token should be valid" ResourceKey="Lifetime">Lifetime:</Label>
|
||||
<Label Class="col-sm-3" For="emailclaimtype" HelpText="Optionally specify the type name of the email address claim provided by the provider. The typical value is 'email'," ResourceKey="EmailClaimType">Email Claim:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="lifetime" class="form-control" @bind="@_lifetime" />
|
||||
<input id="emailclaimtype" class="form-control" @bind="@_emailclaimtype" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="token" HelpText="Select the Create Token button to generate a long-lived access token (valid for 1 year). Be sure to store this token in a safe location as you will not be able to access it in the future." ResourceKey="Token">Access Token:</Label>
|
||||
<Label Class="col-sm-3" For="roleclaimtype" HelpText="The name of the roles claim provided by the provider" ResourceKey="RoleClaimType">Roles Claim:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="roleclaimtype" class="form-control" @bind="@_roleclaimtype" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="roleclaimmappings" HelpText="Optionally provide a comma delimited list of role names provided by the identity provider, as well as mappings to your site roles." ResourceKey="RoleClaimMappings">Role Claim Mappings:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="roleclaimmappings" class="form-control" @bind="@_roleclaimmappings" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="synchronizeroles" HelpText="This option will add or remove role assignments so that the site roles exactly match the roles provided by the identity provider" ResourceKey="SynchronizeRoles">Synchronize Roles?</Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<input id="token" class="form-control" @bind="@_token" />
|
||||
<button type="button" class="btn btn-secondary" @onclick="@CreateToken">@Localizer["CreateToken"]</button>
|
||||
<select id="synchronizeroles" class="form-select" @bind="@_synchronizeroles" required>
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
}
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="profileclaimtypes" HelpText="A comma delimited list of user profile claims provided by the provider, as well as mappings to your user profile definition. For example if the provider includes a 'given_name' claim and you have a 'FirstName' user profile definition you should specify 'given_name:FirstName'." ResourceKey="ProfileClaimTypes">User Profile Claims:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="profileclaimtypes" class="form-control" @bind="@_profileclaimtypes" />
|
||||
</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">
|
||||
<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">
|
||||
<input id="domainfilter" class="form-control" @bind="@_domainfilter" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="createusers" HelpText="Do you want new users to be created automatically? If you disable this option, users must already be registered on the site in order to sign in with their external login." ResourceKey="CreateUsers">Create New Users?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="createusers" class="form-select" @bind="@_createusers">
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="verifyusers" HelpText="Do you want existing users to perform an additional email verification step to link their external login? If you disable this option, existing users will be linked automatically." ResourceKey="VerifyUsers">Verify Existing Users?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="verifyusers" class="form-select" @bind="@_verifyusers">
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="allowhostrole" HelpText="Indicate if host roles are supported from the identity provider. Please use caution with this option as it allows the host user to administrate every site within your installation." ResourceKey="AllowHostRole">Allow Host Role?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="allowhostrole" class="form-select" @bind="@_allowhostrole" required>
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</Section>
|
||||
<Section Name="Token" Heading="Token Settings" ResourceKey="TokenSettings">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="jwtsecret" HelpText="If you want to want to provide API access, please specify a secret which will be used to encrypt your tokens. The secret should be 16 characters or more to ensure optimal security. Please note that if you change this secret, all existing tokens will become invalid and will need to be regenerated." ResourceKey="Secret">Secret:</Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<input type="@_secrettype" id="jwtsecret" class="form-control" @bind="@_secret" />
|
||||
<button type="button" class="btn btn-secondary" @onclick="@ToggleSecret">@_togglesecret</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="issuer" HelpText="Optionally provide the issuer of the token" ResourceKey="Issuer">Issuer:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="issuer" class="form-control" @bind="@_issuer" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="audience" HelpText="Optionally provide the audience for the token" ResourceKey="Audience">Audience:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="audience" class="form-control" @bind="@_audience" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="lifetime" HelpText="The number of minutes for which a token should be valid" ResourceKey="Lifetime">Lifetime:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="lifetime" class="form-control" @bind="@_lifetime" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="token" HelpText="Select the Create Token button to generate a long-lived access token (valid for 1 year). Be sure to store this token in a safe location as you will not be able to access it in the future." ResourceKey="Token">Access Token:</Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<input id="token" class="form-control" @bind="@_token" />
|
||||
<button type="button" class="btn btn-secondary" @onclick="@CreateToken">@Localizer["CreateToken"]</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
</div>
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="SaveSiteSettings">@SharedLocalizer["Save"]</button>
|
||||
@@ -542,13 +545,16 @@ else
|
||||
@code {
|
||||
private List<UserRole> users;
|
||||
private string _deleted = "false";
|
||||
private int _page = 1;
|
||||
|
||||
private string _allowsitelogin;
|
||||
private string _twofactor;
|
||||
private string _loginlink;
|
||||
private string _passkeys;
|
||||
private string _allowregistration;
|
||||
private string _registerurl;
|
||||
private string _profileurl;
|
||||
private string _requireconfirmedemail;
|
||||
private string _passkeys;
|
||||
private string _twofactor;
|
||||
private string _profileurl;
|
||||
private string _cookiename;
|
||||
private string _cookiedomain;
|
||||
private string _cookieexpiration;
|
||||
@@ -598,7 +604,6 @@ else
|
||||
private string _createusers;
|
||||
private string _verifyusers;
|
||||
private string _allowhostrole;
|
||||
private string _allowsitelogin;
|
||||
|
||||
private string _secret;
|
||||
private string _secrettype = "password";
|
||||
@@ -622,11 +627,13 @@ else
|
||||
|
||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
_registerurl = SettingService.GetSetting(settings, "LoginOptions:RegisterUrl", "");
|
||||
_profileurl = SettingService.GetSetting(settings, "LoginOptions:ProfileUrl", "");
|
||||
_requireconfirmedemail = SettingService.GetSetting(settings, "LoginOptions:RequireConfirmedEmail", "true");
|
||||
_passkeys = SettingService.GetSetting(settings, "LoginOptions:Passkeys", "false");
|
||||
_allowsitelogin = SettingService.GetSetting(settings, "LoginOptions:AllowSiteLogin", "true");
|
||||
_twofactor = SettingService.GetSetting(settings, "LoginOptions:TwoFactor", "false");
|
||||
_loginlink = SettingService.GetSetting(settings, "LoginOptions:LoginLink", "false");
|
||||
_passkeys = SettingService.GetSetting(settings, "LoginOptions:Passkeys", "false");
|
||||
_registerurl = SettingService.GetSetting(settings, "LoginOptions:RegisterUrl", "");
|
||||
_requireconfirmedemail = SettingService.GetSetting(settings, "LoginOptions:RequireConfirmedEmail", "true");
|
||||
_profileurl = SettingService.GetSetting(settings, "LoginOptions:ProfileUrl", "");
|
||||
_cookiename = SettingService.GetSetting(settings, "LoginOptions:CookieName", ".AspNetCore.Identity.Application");
|
||||
_cookiedomain = SettingService.GetSetting(settings, "LoginOptions:CookieDomain", "");
|
||||
_cookieexpiration = SettingService.GetSetting(settings, "LoginOptions:CookieExpiration", "");
|
||||
@@ -688,7 +695,6 @@ else
|
||||
_createusers = SettingService.GetSetting(settings, "ExternalLogin:CreateUsers", "true");
|
||||
_verifyusers = SettingService.GetSetting(settings, "ExternalLogin:VerifyUsers", "true");
|
||||
_allowhostrole = SettingService.GetSetting(settings, "ExternalLogin:AllowHostRole", "false");
|
||||
_allowsitelogin = SettingService.GetSetting(settings, "LoginOptions:AllowSiteLogin", "true");
|
||||
}
|
||||
|
||||
private async Task LoadUsersAsync()
|
||||
@@ -753,19 +759,21 @@ else
|
||||
{
|
||||
try
|
||||
{
|
||||
var site = PageState.Site;
|
||||
site.AllowRegistration = bool.Parse(_allowregistration);
|
||||
await SiteService.UpdateSiteAsync(site);
|
||||
|
||||
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
|
||||
|
||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:RegisterUrl", _registerurl, false);
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:ProfileUrl", _profileurl, false);
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:RequireConfirmedEmail", _requireconfirmedemail, false);
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:Passkeys", _passkeys, false);
|
||||
var site = PageState.Site;
|
||||
site.AllowRegistration = bool.Parse(_allowregistration);
|
||||
await SiteService.UpdateSiteAsync(site);
|
||||
|
||||
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
|
||||
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:AllowSiteLogin", _allowsitelogin, false);
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:TwoFactor", _twofactor, false);
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:LoginLink", _loginlink, false);
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:Passkeys", _passkeys, false);
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:RegisterUrl", _registerurl, false);
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:RequireConfirmedEmail", _requireconfirmedemail, false);
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:ProfileUrl", _profileurl, false);
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:CookieName", _cookiename, true);
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:CookieDomain", _cookiedomain, true);
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:CookieExpiration", _cookieexpiration, true);
|
||||
@@ -811,16 +819,15 @@ else
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:CreateUsers", _createusers, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:VerifyUsers", _verifyusers, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:AllowHostRole", _allowhostrole, true);
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:AllowSiteLogin", _allowsitelogin, false);
|
||||
|
||||
settings = SettingService.SetSetting(settings, "JwtOptions:Secret", _secret, true);
|
||||
settings = SettingService.SetSetting(settings, "JwtOptions:Issuer", _issuer, true);
|
||||
settings = SettingService.SetSetting(settings, "JwtOptions:Audience", _audience, true);
|
||||
settings = SettingService.SetSetting(settings, "JwtOptions:Lifetime", _lifetime, true);
|
||||
}
|
||||
|
||||
await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId);
|
||||
await SettingService.ClearSiteSettingsCacheAsync();
|
||||
await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId);
|
||||
await SettingService.ClearSiteSettingsCacheAsync();
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(_secret))
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
@namespace Oqtane.Modules.Admin.Users
|
||||
@inherits ModuleBase
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IUserService UserService
|
||||
@inject ISiteTaskService SiteTaskService
|
||||
@inject IStringLocalizer<Users> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
|
||||
@@ -43,17 +43,9 @@
|
||||
var fileid = _filemanager.GetFileId();
|
||||
if (fileid != -1)
|
||||
{
|
||||
ShowProgressIndicator();
|
||||
var results = await UserService.ImportUsersAsync(PageState.Site.SiteId, fileid, bool.Parse(_notify));
|
||||
if (bool.Parse(results["Success"]))
|
||||
{
|
||||
AddModuleMessage(string.Format(Localizer["Message.Import.Success"], results["Users"]), MessageType.Success);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.Import.Failure"], MessageType.Error);
|
||||
}
|
||||
HideProgressIndicator();
|
||||
var siteTask = new SiteTask(PageState.Site.SiteId, "Import Users", "Oqtane.Infrastructure.ImportUsersTask, Oqtane.Server", $"{fileid}:{_notify}");
|
||||
await SiteTaskService.AddSiteTaskAsync(siteTask);
|
||||
AddModuleMessage(Localizer["Message.Import.Success"], MessageType.Success);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -35,11 +35,11 @@
|
||||
{
|
||||
if (Disabled)
|
||||
{
|
||||
<button type="button" class="@Class" disabled>@((MarkupString)_openIconSpan) @_openText</button>
|
||||
<button type="button" class="@Class" title="@AltText" disabled>@((MarkupString)_openIconSpan) @_openText</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button type="button" class="@Class" @onclick="DisplayModal">@((MarkupString)_openIconSpan) @_openText</button>
|
||||
<button type="button" class="@Class" title="@AltText" @onclick="DisplayModal">@((MarkupString)_openIconSpan) @_openText</button>
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -83,13 +83,13 @@ else
|
||||
{
|
||||
if (Disabled)
|
||||
{
|
||||
<button type="button" class="@Class" disabled>@((MarkupString)_openIconSpan) @_openText</button>
|
||||
<button type="button" title="@AltText" class="@Class" disabled>@((MarkupString)_openIconSpan) @_openText</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<form method="post" class="app-form-inline" @formname="@($"ActionDialogActionForm:{ModuleState.PageModuleId}:{Id}")" @onsubmit="DisplayModal" data-enhance>
|
||||
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||
<button type="submit" class="@Class">@((MarkupString)_openIconSpan) @_openText</button>
|
||||
<button type="submit" title="@AltText" class="@Class">@((MarkupString)_openIconSpan) @_openText</button>
|
||||
</form>
|
||||
}
|
||||
}
|
||||
@@ -112,6 +112,9 @@ else
|
||||
|
||||
[Parameter]
|
||||
public string Text { get; set; } // optional - defaults to Action if not specified
|
||||
|
||||
[Parameter]
|
||||
public string AltText { get; set; } // optional
|
||||
|
||||
[Parameter]
|
||||
public string Action { get; set; } // optional
|
||||
|
||||
@@ -8,17 +8,17 @@
|
||||
{
|
||||
if (Disabled)
|
||||
{
|
||||
<NavLink class="@($"{_classname} disabled")" href="@_url" style="@_style">@((MarkupString)_iconSpan) @_text</NavLink>
|
||||
<NavLink class="@($"{_classname} disabled")" title="@AltText" href="@_url" style="@_style">@((MarkupString)_iconSpan) @_text</NavLink>
|
||||
}
|
||||
else
|
||||
{
|
||||
if (OnClick == null)
|
||||
{
|
||||
<NavLink class="@_classname" href="@_url" style="@_style">@((MarkupString)_iconSpan) @_text</NavLink>
|
||||
<NavLink class="@_classname" title="@AltText" href="@_url" style="@_style">@((MarkupString)_iconSpan) @_text</NavLink>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button type="button" class="@_classname" style="@_style" onclick="@OnClick">@((MarkupString)_iconSpan) @_text</button>
|
||||
<button type="button" class="@_classname" title="@AltText" style="@_style" onclick="@OnClick">@((MarkupString)_iconSpan) @_text</button>
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,9 @@
|
||||
[Parameter]
|
||||
public string Text { get; set; } // optional - defaults to Action if not specified
|
||||
|
||||
[Parameter]
|
||||
public string AltText { get; set; } // optional
|
||||
|
||||
[Parameter]
|
||||
public int ModuleId { get; set; } = -1; // optional - allows the link to target a specific moduleid
|
||||
|
||||
|
||||
@@ -61,6 +61,12 @@
|
||||
{
|
||||
<input type="file" id="@_fileinputid" name="file" accept="@_filter" />
|
||||
}
|
||||
@if (MaxUploadFileSize > 0)
|
||||
{
|
||||
<div class="row my-1">
|
||||
<small class="fw-light">@string.Format(Localizer["File.MaxSize"], MaxUploadFileSize)</small>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<button type="button" class="btn btn-success" @onclick="UploadFiles">@SharedLocalizer["Upload"]</button>
|
||||
@@ -163,6 +169,9 @@
|
||||
[Parameter]
|
||||
public int ChunkSize { get; set; } = 1; // optional - size of file chunks to upload in MB
|
||||
|
||||
[Parameter]
|
||||
public int MaxUploadFileSize { get; set; } = -1; // optional - maximum upload file size in MB
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<int> OnUpload { get; set; } // optional - executes a method in the calling component when a file is uploaded
|
||||
|
||||
@@ -381,16 +390,39 @@
|
||||
if (uploads.Length > 0)
|
||||
{
|
||||
string restricted = "";
|
||||
string tooLarge = "";
|
||||
foreach (var upload in uploads)
|
||||
{
|
||||
var filename = upload.Split(':')[0];
|
||||
var fileparts = upload.Split(':');
|
||||
var filename = fileparts[0];
|
||||
|
||||
if (MaxUploadFileSize > 0)
|
||||
{
|
||||
var filesizeBytes = long.Parse(fileparts[1]);
|
||||
var filesizeMB = (double)filesizeBytes / (1024 * 1024);
|
||||
if (filesizeMB > MaxUploadFileSize)
|
||||
{
|
||||
tooLarge += (tooLarge == "" ? "" : ",") + filename;
|
||||
}
|
||||
}
|
||||
|
||||
var extension = (filename.LastIndexOf(".") != -1) ? filename.Substring(filename.LastIndexOf(".") + 1) : "";
|
||||
if (!PageState.Site.UploadableFiles.Split(',').Contains(extension.ToLower()))
|
||||
{
|
||||
restricted += (restricted == "" ? "" : ",") + extension;
|
||||
}
|
||||
}
|
||||
if (restricted == "")
|
||||
if (restricted != "")
|
||||
{
|
||||
_message = string.Format(Localizer["Message.File.Restricted"], restricted);
|
||||
_messagetype = MessageType.Warning;
|
||||
}
|
||||
else if (tooLarge != "")
|
||||
{
|
||||
_message = string.Format(Localizer["Message.File.TooLarge"], tooLarge, MaxUploadFileSize);
|
||||
_messagetype = MessageType.Warning;
|
||||
}
|
||||
else
|
||||
{
|
||||
CancellationTokenSource tokenSource = new CancellationTokenSource();
|
||||
|
||||
@@ -490,11 +522,6 @@
|
||||
tokenSource.Dispose();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_message = string.Format(Localizer["Message.File.Restricted"], restricted);
|
||||
_messagetype = MessageType.Warning;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
@if (_style == MessageStyle.Toast)
|
||||
{
|
||||
<div class="app-modulemessage-toast bottom-0 end-0" @key="DateTime.UtcNow">
|
||||
<div class="app-modulemessage-toast bottom-0 end-0">
|
||||
<div class="@_classname alert-dismissible fade show mb-3 rounded-end-0" role="alert">
|
||||
@((MarkupString)Message)
|
||||
@if (Type == MessageType.Error && PageState != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
@inject ISettingService SettingService
|
||||
@inject IStringLocalizer<RichTextEditor> Localizer
|
||||
|
||||
<div class="row" style="margin-bottom: 50px;">
|
||||
<div class="row" style="@_style">
|
||||
<div class="col">
|
||||
@_textEditorComponent
|
||||
</div>
|
||||
@@ -18,6 +18,8 @@
|
||||
private RenderFragment _textEditorComponent;
|
||||
private ITextEditor _textEditor;
|
||||
|
||||
private string _style = "margin-bottom: 50px;";
|
||||
|
||||
[Parameter]
|
||||
public string Content { get; set; }
|
||||
|
||||
@@ -30,6 +32,9 @@
|
||||
[Parameter]
|
||||
public string Provider { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string Style { get; set; } // optional
|
||||
|
||||
[Parameter(CaptureUnmatchedValues = true)]
|
||||
public Dictionary<string, object> AdditionalAttributes { get; set; } = new Dictionary<string, object>();
|
||||
|
||||
@@ -40,6 +45,12 @@
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
|
||||
if (!string.IsNullOrEmpty(Style))
|
||||
{
|
||||
_style = Style;
|
||||
}
|
||||
|
||||
_textEditorComponent = (builder) =>
|
||||
{
|
||||
CreateTextEditor(builder);
|
||||
|
||||
@@ -30,6 +30,12 @@ else
|
||||
[Parameter]
|
||||
public SecurityAccessLevel? Security { get; set; } // optional - can be used to specify SecurityAccessLevel
|
||||
|
||||
[Parameter]
|
||||
public string RoleName { get; set; } // optional - can be used to specify Role allowed to view this tab
|
||||
|
||||
[Parameter]
|
||||
public string PermissionName { get; set; } // optional - can be used to specify Permission allowed to view this tab
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
base.OnParametersSet();
|
||||
|
||||
@@ -84,14 +84,63 @@
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if a tab should be visible based on user permissions.
|
||||
/// Authorization follows this hierarchy:
|
||||
/// 1. Host tabs (Security == Host): Only users with Host role can access (Admins excluded)
|
||||
/// 2. Admin users: Bypass all other checks (except Host restrictions)
|
||||
/// 3. SecurityAccessLevel check (null/Anonymous/View/Edit/Host):
|
||||
/// - null: No security level restriction (proceeds to step 4)
|
||||
/// - Anonymous: No authentication required
|
||||
/// - View/Edit: Requires corresponding module permission
|
||||
/// - Host: Only Host role can access
|
||||
/// 4. Additional RoleName requirement (if specified)
|
||||
/// 5. Additional PermissionName requirement (if specified)
|
||||
///
|
||||
/// Important: When Security is null, RoleName and PermissionName checks STILL apply
|
||||
/// (Security = null doesn't mean unrestricted, it means "no security level required")
|
||||
/// </summary>
|
||||
/// <param name="tabPanel">The tab panel to check authorization for</param>
|
||||
/// <returns>True if user is authorized to see this tab, false otherwise</returns>
|
||||
private bool IsAuthorized(TabPanel tabPanel)
|
||||
{
|
||||
var authorized = false;
|
||||
// Step 1: Check for Host-only restriction
|
||||
if (tabPanel.Security == SecurityAccessLevel.Host)
|
||||
{
|
||||
return UserSecurity.IsAuthorized(PageState.User, RoleNames.Host);
|
||||
}
|
||||
|
||||
// Step 2: Admin bypass all restrictions except Host
|
||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Step 3: If Security is null, check only RoleName and PermissionName
|
||||
if (tabPanel.Security == null)
|
||||
{
|
||||
// Start with authorized = true for null security
|
||||
bool isAuthorized = true;
|
||||
|
||||
// Only apply RoleName check if provided
|
||||
if (!string.IsNullOrEmpty(tabPanel.RoleName))
|
||||
{
|
||||
isAuthorized = UserSecurity.IsAuthorized(PageState.User, tabPanel.RoleName);
|
||||
}
|
||||
|
||||
// Only apply PermissionName check if provided
|
||||
if (isAuthorized && !string.IsNullOrEmpty(tabPanel.PermissionName))
|
||||
{
|
||||
isAuthorized = UserSecurity.IsAuthorized(PageState.User, tabPanel.PermissionName, ModuleState.PermissionList);
|
||||
}
|
||||
|
||||
return isAuthorized;
|
||||
}
|
||||
|
||||
// Handle other SecurityAccessLevel values
|
||||
bool authorized = false; // Use different variable name or move declaration
|
||||
switch (tabPanel.Security)
|
||||
{
|
||||
case null: // security not specified - assume SecurityAccessLevel.Anonymous
|
||||
authorized = true;
|
||||
break;
|
||||
case SecurityAccessLevel.Anonymous:
|
||||
authorized = true;
|
||||
break;
|
||||
@@ -101,13 +150,23 @@
|
||||
case SecurityAccessLevel.Edit:
|
||||
authorized = UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, ModuleState.PermissionList);
|
||||
break;
|
||||
case SecurityAccessLevel.Admin:
|
||||
authorized = UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin);
|
||||
break;
|
||||
case SecurityAccessLevel.Host:
|
||||
authorized = UserSecurity.IsAuthorized(PageState.User, RoleNames.Host);
|
||||
break;
|
||||
}
|
||||
|
||||
// Step 4: Additional RoleName requirement
|
||||
if (authorized && !string.IsNullOrEmpty(tabPanel.RoleName))
|
||||
{
|
||||
authorized = UserSecurity.IsAuthorized(PageState.User, tabPanel.RoleName);
|
||||
}
|
||||
|
||||
// Step 5: Additional PermissionName requirement
|
||||
if (authorized && !string.IsNullOrEmpty(tabPanel.PermissionName))
|
||||
{
|
||||
authorized = UserSecurity.IsAuthorized(PageState.User, tabPanel.PermissionName, ModuleState.PermissionList);
|
||||
}
|
||||
|
||||
return authorized;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ namespace Oqtane.Modules.Controls
|
||||
{ "FormatBlock", (builder, sequence) => CreateFragment(builder, sequence, "FormatBlock", "RadzenHtmlEditorFormatBlock") },
|
||||
{ "Indent", (builder, sequence) => CreateFragment(builder, sequence, "Indent", "RadzenHtmlEditorIndent") },
|
||||
{ "InsertImage", (builder, sequence) => CreateFragment(builder, sequence, "InsertImage", "RadzenHtmlEditorCustomTool", "InsertImage", "image") },
|
||||
{ "Bold", (builder, sequence) => CreateFragment(builder, sequence, "Italic", "RadzenHtmlEditorBold") },
|
||||
{ "Italic", (builder, sequence) => CreateFragment(builder, sequence, "Italic", "RadzenHtmlEditorItalic") },
|
||||
{ "Justify", (builder, sequence) => CreateFragment(builder, sequence, "Justify", "RadzenHtmlEditorJustify") },
|
||||
{ "Link", (builder, sequence) => CreateFragment(builder, sequence, "InsertLink", "RadzenHtmlEditorCustomTool", "InsertLink", "insert_link") },
|
||||
|
||||
@@ -31,8 +31,8 @@
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th>@SharedLocalizer["CreatedOn"]</th>
|
||||
<th>@SharedLocalizer["CreatedBy"]</th>
|
||||
<th>@Localizer["CreatedOn"]</th>
|
||||
<th>@Localizer["CreatedBy"]</th>
|
||||
</Header>
|
||||
<Row>
|
||||
<td><ActionLink Action="View" Security="SecurityAccessLevel.Edit" OnClick="@(async () => await View(context))" ResourceKey="View" /></td>
|
||||
@@ -122,13 +122,9 @@
|
||||
{
|
||||
try
|
||||
{
|
||||
htmltext = await HtmlTextService.GetHtmlTextAsync(htmltext.HtmlTextId, htmltext.ModuleId);
|
||||
if (htmltext != null)
|
||||
{
|
||||
_view = htmltext.Content;
|
||||
_view = Utilities.FormatContent(_view, PageState.Alias, "render");
|
||||
StateHasChanged();
|
||||
}
|
||||
_view = htmltext.Content;
|
||||
_view = Utilities.FormatContent(_view, PageState.Alias, "render");
|
||||
StateHasChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -141,19 +137,15 @@
|
||||
{
|
||||
try
|
||||
{
|
||||
htmltext = await HtmlTextService.GetHtmlTextAsync(htmltext.HtmlTextId, ModuleState.ModuleId);
|
||||
if (htmltext != null)
|
||||
{
|
||||
var content = htmltext.Content;
|
||||
htmltext = new HtmlText();
|
||||
htmltext.ModuleId = ModuleState.ModuleId;
|
||||
htmltext.Content = content;
|
||||
await HtmlTextService.AddHtmlTextAsync(htmltext);
|
||||
await logger.LogInformation("Content Restored {HtmlText}", htmltext);
|
||||
AddModuleMessage(Localizer["Message.Content.Restored"], MessageType.Success);
|
||||
await LoadContent();
|
||||
StateHasChanged();
|
||||
}
|
||||
var content = htmltext.Content;
|
||||
htmltext = new HtmlText();
|
||||
htmltext.ModuleId = ModuleState.ModuleId;
|
||||
htmltext.Content = content;
|
||||
await HtmlTextService.AddHtmlTextAsync(htmltext);
|
||||
await logger.LogInformation("Content Restored {HtmlText}", htmltext);
|
||||
AddModuleMessage(Localizer["Message.Content.Restored"], MessageType.Success);
|
||||
await LoadContent();
|
||||
StateHasChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -166,15 +158,11 @@
|
||||
{
|
||||
try
|
||||
{
|
||||
htmltext = await HtmlTextService.GetHtmlTextAsync(htmltext.HtmlTextId, ModuleState.ModuleId);
|
||||
if (htmltext != null)
|
||||
{
|
||||
await HtmlTextService.DeleteHtmlTextAsync(htmltext.HtmlTextId, htmltext.ModuleId);
|
||||
await logger.LogInformation("Content Deleted {HtmlText}", htmltext);
|
||||
AddModuleMessage(Localizer["Message.Content.Deleted"], MessageType.Success);
|
||||
await LoadContent();
|
||||
StateHasChanged();
|
||||
}
|
||||
await HtmlTextService.DeleteHtmlTextAsync(htmltext.HtmlTextId, htmltext.ModuleId);
|
||||
await logger.LogInformation("Content Deleted {HtmlText}", htmltext);
|
||||
AddModuleMessage(Localizer["Message.Content.Deleted"], MessageType.Success);
|
||||
await LoadContent();
|
||||
StateHasChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
@@ -7,6 +7,18 @@ using Oqtane.Shared;
|
||||
|
||||
namespace Oqtane.Modules.HtmlText.Services
|
||||
{
|
||||
[PrivateApi("Mark HtmlText classes as private, since it's not very useful in the public docs")]
|
||||
public interface IHtmlTextService
|
||||
{
|
||||
Task<List<Models.HtmlText>> GetHtmlTextsAsync(int moduleId);
|
||||
|
||||
Task<Models.HtmlText> GetHtmlTextAsync(int moduleId);
|
||||
|
||||
Task<Models.HtmlText> AddHtmlTextAsync(Models.HtmlText htmltext);
|
||||
|
||||
Task DeleteHtmlTextAsync(int htmlTextId, int moduleId);
|
||||
}
|
||||
|
||||
[PrivateApi("Mark HtmlText classes as private, since it's not very useful in the public docs")]
|
||||
public class HtmlTextService : ServiceBase, IHtmlTextService, IClientService
|
||||
{
|
||||
@@ -24,11 +36,6 @@ namespace Oqtane.Modules.HtmlText.Services
|
||||
return await GetJsonAsync<Models.HtmlText>(CreateAuthorizationPolicyUrl($"{ApiUrl}/{moduleId}", EntityNames.Module, moduleId));
|
||||
}
|
||||
|
||||
public async Task<Models.HtmlText> GetHtmlTextAsync(int htmlTextId, int moduleId)
|
||||
{
|
||||
return await GetJsonAsync<Models.HtmlText>(CreateAuthorizationPolicyUrl($"{ApiUrl}/{htmlTextId}/{moduleId}", EntityNames.Module, moduleId));
|
||||
}
|
||||
|
||||
public async Task<Models.HtmlText> AddHtmlTextAsync(Models.HtmlText htmlText)
|
||||
{
|
||||
return await PostJsonAsync(CreateAuthorizationPolicyUrl($"{ApiUrl}", EntityNames.Module, htmlText.ModuleId), htmlText);
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Oqtane.Documentation;
|
||||
|
||||
namespace Oqtane.Modules.HtmlText.Services
|
||||
{
|
||||
[PrivateApi("Mark HtmlText classes as private, since it's not very useful in the public docs")]
|
||||
public interface IHtmlTextService
|
||||
{
|
||||
Task<List<Models.HtmlText>> GetHtmlTextsAsync(int moduleId);
|
||||
|
||||
Task<Models.HtmlText> GetHtmlTextAsync(int moduleId);
|
||||
|
||||
Task<Models.HtmlText> GetHtmlTextAsync(int htmlTextId, int moduleId);
|
||||
|
||||
Task<Models.HtmlText> AddHtmlTextAsync(Models.HtmlText htmltext);
|
||||
|
||||
Task DeleteHtmlTextAsync(int htmlTextId, int moduleId);
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,12 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="versions" ResourceKey="Versions" ResourceType="@resourceType" HelpText="The number of content versions to preserve (note that zero means unlimited)">Versions: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="versions" type="number" min="0" max="9" step="1" class="form-control" @bind="@_versions" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -26,12 +32,14 @@
|
||||
private bool validated = false;
|
||||
|
||||
private string _dynamictokens;
|
||||
private string _versions = "5";
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
try
|
||||
{
|
||||
_dynamictokens = SettingService.GetSetting(ModuleState.Settings, "DynamicTokens", "false");
|
||||
_versions = SettingService.GetSetting(ModuleState.Settings, "Versions", "5");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -45,6 +53,10 @@
|
||||
{
|
||||
var settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId);
|
||||
settings = SettingService.SetSetting(settings, "DynamicTokens", _dynamictokens);
|
||||
if (int.TryParse(_versions, out int versions) && versions >= 0 && versions <= 9)
|
||||
{
|
||||
settings = SettingService.SetSetting(settings, "Versions", versions.ToString());
|
||||
}
|
||||
await SettingService.UpdateModuleSettingsAsync(settings, ModuleState.ModuleId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -372,6 +372,11 @@ namespace Oqtane.Modules
|
||||
}
|
||||
|
||||
// UI methods
|
||||
private static readonly string RenderModeBoundaryErrorMessage =
|
||||
"RenderModeBoundary is not available. This method requires a RenderModeBoundary parameter. " +
|
||||
"If you are using child components, ensure you pass the RenderModeBoundary property to the child component: " +
|
||||
"<ChildComponent RenderModeBoundary=\"RenderModeBoundary\" />";
|
||||
|
||||
public void AddModuleMessage(string message, MessageType type)
|
||||
{
|
||||
AddModuleMessage(message, type, "top");
|
||||
@@ -389,21 +394,37 @@ namespace Oqtane.Modules
|
||||
|
||||
public void AddModuleMessage(string message, MessageType type, string position, MessageStyle style)
|
||||
{
|
||||
if (RenderModeBoundary == null)
|
||||
{
|
||||
throw new InvalidOperationException(RenderModeBoundaryErrorMessage);
|
||||
}
|
||||
RenderModeBoundary.AddModuleMessage(message, type, position, style);
|
||||
}
|
||||
|
||||
public void ClearModuleMessage()
|
||||
{
|
||||
if (RenderModeBoundary == null)
|
||||
{
|
||||
throw new InvalidOperationException(RenderModeBoundaryErrorMessage);
|
||||
}
|
||||
RenderModeBoundary.AddModuleMessage("", MessageType.Undefined);
|
||||
}
|
||||
|
||||
public void ShowProgressIndicator()
|
||||
{
|
||||
if (RenderModeBoundary == null)
|
||||
{
|
||||
throw new InvalidOperationException(RenderModeBoundaryErrorMessage);
|
||||
}
|
||||
RenderModeBoundary.ShowProgressIndicator();
|
||||
}
|
||||
|
||||
public void HideProgressIndicator()
|
||||
{
|
||||
if (RenderModeBoundary == null)
|
||||
{
|
||||
throw new InvalidOperationException(RenderModeBoundaryErrorMessage);
|
||||
}
|
||||
RenderModeBoundary.HideProgressIndicator();
|
||||
}
|
||||
|
||||
@@ -460,6 +481,11 @@ namespace Oqtane.Modules
|
||||
|
||||
public string ReplaceTokens(string content, object obj)
|
||||
{
|
||||
// check for null or empty content
|
||||
if (string.IsNullOrEmpty(content))
|
||||
{
|
||||
return content;
|
||||
}
|
||||
// Using StringBuilder avoids the performance penalty of repeated string allocations
|
||||
// that occur with string.Replace or string concatenation inside loops.
|
||||
var sb = new StringBuilder();
|
||||
|
||||
@@ -8,11 +8,11 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0" />
|
||||
<PackageReference Include="Radzen.Blazor" Version="8.3.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="10.0.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="10.0.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.5" />
|
||||
<PackageReference Include="Radzen.Blazor" Version="10.0.6" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
174
Oqtane.Client/Resources/Modules/Admin/GlobalReplace/Index.resx
Normal file
174
Oqtane.Client/Resources/Modules/Admin/GlobalReplace/Index.resx
Normal file
@@ -0,0 +1,174 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="CaseSensitive.Text" xml:space="preserve">
|
||||
<value>Match Case?</value>
|
||||
</data>
|
||||
<data name="CaseSensitive.HelpText" xml:space="preserve">
|
||||
<value>Specify if the replacement operation should be case sensitive</value>
|
||||
</data>
|
||||
<data name="Content.Text" xml:space="preserve">
|
||||
<value>Module Content?</value>
|
||||
</data>
|
||||
<data name="Content.HelpText" xml:space="preserve">
|
||||
<value>Specify if module content should be updated</value>
|
||||
</data>
|
||||
<data name="Pages.Text" xml:space="preserve">
|
||||
<value>Page Info?</value>
|
||||
</data>
|
||||
<data name="Pages.HelpText" xml:space="preserve">
|
||||
<value>Specify if page information should be updated (ie. name, title, head content, body content settings)</value>
|
||||
</data>
|
||||
<data name="Site.Text" xml:space="preserve">
|
||||
<value>Site Info?</value>
|
||||
</data>
|
||||
<data name="Site.HelpText" xml:space="preserve">
|
||||
<value>Specify if site information should be updated (ie. name, head content, body content, settings)</value>
|
||||
</data>
|
||||
<data name="Replace.Text" xml:space="preserve">
|
||||
<value>Replace With:</value>
|
||||
</data>
|
||||
<data name="Replace.HelpText" xml:space="preserve">
|
||||
<value>Specify the replacement content</value>
|
||||
</data>
|
||||
<data name="Modules.Text" xml:space="preserve">
|
||||
<value>Module Info?</value>
|
||||
</data>
|
||||
<data name="Modules.HelpText" xml:space="preserve">
|
||||
<value>Specify if module information should be updated (ie. title, header, footer settings)</value>
|
||||
</data>
|
||||
<data name="Success.Save" xml:space="preserve">
|
||||
<value>Your Global Replace Request Has Been Submitted And Will Be Executed Shortly. Please Be Patient.</value>
|
||||
</data>
|
||||
<data name="Error.Save" xml:space="preserve">
|
||||
<value>Error Saving Global Replace</value>
|
||||
</data>
|
||||
<data name="Find.HelpText" xml:space="preserve">
|
||||
<value>Specify the content which needs to be replaced</value>
|
||||
</data>
|
||||
<data name="Find.Text" xml:space="preserve">
|
||||
<value>Find What:</value>
|
||||
</data>
|
||||
<data name="GlobalReplace.Header" xml:space="preserve">
|
||||
<value>Global Replace</value>
|
||||
</data>
|
||||
<data name="GlobalReplace.Message" xml:space="preserve">
|
||||
<value>This Operation is Permanent. Are You Sure You Wish To Proceed?</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -139,7 +139,7 @@
|
||||
<value>Error Updating Job</value>
|
||||
</data>
|
||||
<data name="Message.Required.JobInfo" xml:space="preserve">
|
||||
<value>You Must Provide The Job Name, Type, Frequency, and Retention</value>
|
||||
<value>You Must Provide The Job Name, Frequency, and Retention</value>
|
||||
</data>
|
||||
<data name="Name.HelpText" xml:space="preserve">
|
||||
<value>Enter the job name</value>
|
||||
@@ -154,7 +154,7 @@
|
||||
<value>Select how often you want the job to run</value>
|
||||
</data>
|
||||
<data name="Starting.HelpText" xml:space="preserve">
|
||||
<value>Optionally enter the date and time when this job should start executing</value>
|
||||
<value>Optionally enter the date and time when this job should start executing. If no date or time is specified, the job will execute immediately.</value>
|
||||
</data>
|
||||
<data name="Ending.HelpText" xml:space="preserve">
|
||||
<value>Optionally enter the date and time when this job should stop executing</value>
|
||||
@@ -163,7 +163,7 @@
|
||||
<value>Number of log entries to retain for this job</value>
|
||||
</data>
|
||||
<data name="NextExecution.HelpText" xml:space="preserve">
|
||||
<value>Optionally modify the date and time when this job should execute next</value>
|
||||
<value>The date and time when this job will execute next. This value cannot be modified. Use the settings above to control the execution of the job.</value>
|
||||
</data>
|
||||
<data name="Type.Text" xml:space="preserve">
|
||||
<value>Type: </value>
|
||||
@@ -193,6 +193,15 @@
|
||||
<value>Execute Once</value>
|
||||
</data>
|
||||
<data name="Message.StartEndDateError" xml:space="preserve">
|
||||
<value>Start Date cannot be after End Date.</value>
|
||||
<value>The Start Date Cannot Be Later Than The End Date</value>
|
||||
</data>
|
||||
<data name="Message.StartDateError" xml:space="preserve">
|
||||
<value>The Start Date Cannot Be Prior To The Current Date</value>
|
||||
</data>
|
||||
<data name="Message.ExecutingError" xml:space="preserve">
|
||||
<value>The Job Is Currently Executing. You Must Wait Until The Job Has Completed.</value>
|
||||
</data>
|
||||
<data name="JobNotEditable" xml:space="preserve">
|
||||
<value>The Job Cannot Be Modified As It Is Currently Enabled. You Must Disable The Job To Change Its Settings.</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -120,6 +120,12 @@
|
||||
<data name="ForgotPassword" xml:space="preserve">
|
||||
<value>Forgot Password?</value>
|
||||
</data>
|
||||
<data name="ForgotUsername" xml:space="preserve">
|
||||
<value>Forgot Username?</value>
|
||||
</data>
|
||||
<data name="UseLoginLink" xml:space="preserve">
|
||||
<value>Use Login Link</value>
|
||||
</data>
|
||||
<data name="Success.Account.Verified" xml:space="preserve">
|
||||
<value>User Account Email Address Verified Successfully. You Can Now Login With Your Username And Password.</value>
|
||||
</data>
|
||||
@@ -142,16 +148,19 @@
|
||||
<value>You Are Already Signed In</value>
|
||||
</data>
|
||||
<data name="Message.ForgotPassword" xml:space="preserve">
|
||||
<value>Please Enter The Username Related To Your Account And Then Select The Forgot Password Option Again</value>
|
||||
</data>
|
||||
<data name="Message.ForgotUser" xml:space="preserve">
|
||||
<value>Please Check The Email Address Associated To Your User Account For A Password Reset Notification</value>
|
||||
</data>
|
||||
<data name="Message.ForgotUsername" xml:space="preserve">
|
||||
<value>Please Check Your Email For A Username Reminder Notification</value>
|
||||
</data>
|
||||
<data name="Message.SendLoginLink" xml:space="preserve">
|
||||
<value>A Login Link Has Been Sent To Your Email Address. The Link Is Only Valid For A Limited Amount Of Time.</value>
|
||||
</data>
|
||||
<data name="Message.UserDoesNotExist" xml:space="preserve">
|
||||
<value>User Does Not Exist</value>
|
||||
<value>User Does Not Exist For Criteria Specified</value>
|
||||
</data>
|
||||
<data name="Code.HelpText" xml:space="preserve">
|
||||
<value>Please Enter The Secure Verification Code Which Was Sent To You By Email.</value>
|
||||
<value>Please enter the secure verification code which was sent to you by email</value>
|
||||
</data>
|
||||
<data name="Code.Placeholder" xml:space="preserve">
|
||||
<value>Verification Code</value>
|
||||
@@ -166,7 +175,7 @@
|
||||
<value>A Secure Verification Code Has Been Sent To Your Email Address. Please Enter The Code That You Received. If You Do Not Receive The Code Or You Have Lost Access To Your Email, Please Contact Your Administrator.</value>
|
||||
</data>
|
||||
<data name="Password.HelpText" xml:space="preserve">
|
||||
<value>Please Enter The Password Related To Your Account. Remember That Passwords Are Case Sensitive. If You Attempt Unsuccessfully To Log In To Your Account Multiple Times, You Will Be Locked Out For A Period Of Time.</value>
|
||||
<value>Please enter the password related to your account. Remember that passwords are sase sensitive. If you attempt to login to your account multiple times unsuccessfully, you will be locked out for a period of time.</value>
|
||||
</data>
|
||||
<data name="Password.Placeholder" xml:space="preserve">
|
||||
<value>Password</value>
|
||||
@@ -175,13 +184,13 @@
|
||||
<value>Password:</value>
|
||||
</data>
|
||||
<data name="Remember.HelpText" xml:space="preserve">
|
||||
<value>Specify If You Would Like To Be Signed Back In Automatically The Next Time You Visit This Site</value>
|
||||
<value>Specify if you would like to be signed back in automatically the next time you visit this site</value>
|
||||
</data>
|
||||
<data name="Remember.Text" xml:space="preserve">
|
||||
<value>Remember Me?</value>
|
||||
<value>Stay Signed In?</value>
|
||||
</data>
|
||||
<data name="Username.HelpText" xml:space="preserve">
|
||||
<value>Please Enter The Username Related To Your Account</value>
|
||||
<value>Please enter the username related to your account</value>
|
||||
</data>
|
||||
<data name="Username.Placeholder" xml:space="preserve">
|
||||
<value>Username</value>
|
||||
@@ -201,7 +210,13 @@
|
||||
<data name="Error.ResetPassword" xml:space="preserve">
|
||||
<value>Error Resetting Password</value>
|
||||
</data>
|
||||
<data name="ExternalLoginStatus.DuplicateEmail" xml:space="preserve">
|
||||
<data name="Error.ForgotUsername" xml:space="preserve">
|
||||
<value>Error Sending Username Reminder</value>
|
||||
</data>
|
||||
<data name="Error.SendLoginLink" xml:space="preserve">
|
||||
<value>Error Sending Login Link</value>
|
||||
</data>
|
||||
<data name="ExternalLoginStatus.DuplicateEmail" xml:space="preserve">
|
||||
<value>Multiple User Accounts Already Exist With The Email Address Of Your External Login. Please Contact Your Administrator For Further Instructions.</value>
|
||||
</data>
|
||||
<data name="ExternalLoginStatus.MissingClaims" xml:space="preserve">
|
||||
@@ -228,6 +243,12 @@
|
||||
<data name="ExternalLoginStatus.ReviewClaims" xml:space="preserve">
|
||||
<value>The Review Claims Option Was Enabled In External Login Settings. Please Visit The Event Log To View The Claims Returned By The Provider.</value>
|
||||
</data>
|
||||
<data name="ExternalLoginStatus.LoginLinkFailed" xml:space="preserve">
|
||||
<value>Login Links Are Time Sensitive. Please Request Another Login Link To Complete The Login Process.</value>
|
||||
</data>
|
||||
<data name="ExternalLoginStatus.PasskeyFailed" xml:space="preserve">
|
||||
<value>Passkey Login Was Unsuccessful. Please Ensure You Selected The Correct Passkey For This Site.</value>
|
||||
</data>
|
||||
<data name="Register" xml:space="preserve">
|
||||
<value>Register as new user?</value>
|
||||
</data>
|
||||
@@ -237,4 +258,13 @@
|
||||
<data name="Error.Passkey.Fail" xml:space="preserve">
|
||||
<value>Passkey Login Was Not Successful</value>
|
||||
</data>
|
||||
<data name="Email.HelpText" xml:space="preserve">
|
||||
<value>Please enter the email address related to your account</value>
|
||||
</data>
|
||||
<data name="Email.Placeholder" xml:space="preserve">
|
||||
<value>Email Address</value>
|
||||
</data>
|
||||
<data name="Email.Text" xml:space="preserve">
|
||||
<value>Email:</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -234,4 +234,10 @@
|
||||
<data name="Pages.Heading" xml:space="preserve">
|
||||
<value>Pages</value>
|
||||
</data>
|
||||
<data name="Fingerprint.Text" xml:space="preserve">
|
||||
<value>Fingerprint:</value>
|
||||
</data>
|
||||
<data name="Fingerprint.HelpText" xml:space="preserve">
|
||||
<value>A unique identifier for the module's static resources. This value can be changed by clicking the Save option below (ie. cache busting).</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -130,7 +130,7 @@
|
||||
<value>Indicate if this module should be displayed on all pages</value>
|
||||
</data>
|
||||
<data name="Page.HelpText" xml:space="preserve">
|
||||
<value>The page that the module is located on</value>
|
||||
<value>The page that the module is located on. Please note that shared modules cannot be moved to other pages.</value>
|
||||
</data>
|
||||
<data name="Title.Text" xml:space="preserve">
|
||||
<value>Title: </value>
|
||||
|
||||
@@ -225,9 +225,6 @@
|
||||
<data name="Personalizable.Text" xml:space="preserve">
|
||||
<value>Personalizable? </value>
|
||||
</data>
|
||||
<data name="Appearance.Name" xml:space="preserve">
|
||||
<value>Appearance</value>
|
||||
</data>
|
||||
<data name="HeadContent.HelpText" xml:space="preserve">
|
||||
<value>Optionally enter content to be included in the page head (ie. meta, link, or script tags)</value>
|
||||
</data>
|
||||
@@ -253,7 +250,7 @@
|
||||
<value>Permissions</value>
|
||||
</data>
|
||||
<data name="Theme.Heading" xml:space="preserve">
|
||||
<value>Theme Settings</value>
|
||||
<value>Theme</value>
|
||||
</data>
|
||||
<data name="EffectiveDate.HelpText" xml:space="preserve">
|
||||
<value>The date that this page is active</value>
|
||||
@@ -267,4 +264,7 @@
|
||||
<data name="ExpiryDate.Text" xml:space="preserve">
|
||||
<value>Expiry Date: </value>
|
||||
</data>
|
||||
</root>
|
||||
<data name="Appearance.Heading" xml:space="preserve">
|
||||
<value>Appearance</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -309,4 +309,7 @@
|
||||
<data name="UpdateModulePermissions.HelpText" xml:space="preserve">
|
||||
<value>Specify if changes made to page permissions should be propagated to the modules on this page</value>
|
||||
</data>
|
||||
<data name="Theme.Heading" xml:space="preserve">
|
||||
<value>Theme</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -204,7 +204,7 @@
|
||||
<data name="Success.Page.Delete" xml:space="preserve">
|
||||
<value>Page Deleted Successfully</value>
|
||||
</data>
|
||||
<data name="Success.Pages.Deleted" xml:space="preserve">
|
||||
<data name="Success.Pages.Delete" xml:space="preserve">
|
||||
<value>All Pages Deleted Successfully</value>
|
||||
</data>
|
||||
<data name="Success.Module.Restore" xml:space="preserve">
|
||||
|
||||
@@ -186,4 +186,10 @@
|
||||
<data name="TimeZone.HelpText" xml:space="preserve">
|
||||
<value>Your time zone</value>
|
||||
</data>
|
||||
<data name="CultureCode.Text" xml:space="preserve">
|
||||
<value>Language:</value>
|
||||
</data>
|
||||
<data name="CultureCode.HelpText" xml:space="preserve">
|
||||
<value>Your preferred language. Note that you will only be able to choose from languages supported on this site.</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -132,9 +132,6 @@
|
||||
<data name="DefaultAdminContainer" xml:space="preserve">
|
||||
<value>Default Admin Container</value>
|
||||
</data>
|
||||
<data name="Smtp.Required.EnableNotificationJob" xml:space="preserve">
|
||||
<value>** Please Note That SMTP Requires The Notification Job To Be Enabled In Scheduled Jobs</value>
|
||||
</data>
|
||||
<data name="Smtp.TestConfig" xml:space="preserve">
|
||||
<value>Test SMTP Configuration</value>
|
||||
</data>
|
||||
@@ -322,16 +319,16 @@
|
||||
<value>The default alias for the site. Requests for non-default aliases will be redirected to the default alias.</value>
|
||||
</data>
|
||||
<data name="DefaultAlias.Text" xml:space="preserve">
|
||||
<value>Default Alias: </value>
|
||||
<value>Default?</value>
|
||||
</data>
|
||||
<data name="Aliases.Heading" xml:space="preserve">
|
||||
<value>Site Urls</value>
|
||||
</data>
|
||||
<data name="AliasName" xml:space="preserve">
|
||||
<value>Url</value>
|
||||
<data name="AliasName.Text" xml:space="preserve">
|
||||
<value>Url:</value>
|
||||
</data>
|
||||
<data name="AliasDefault" xml:space="preserve">
|
||||
<value>Default?</value>
|
||||
<data name="Default" xml:space="preserve">
|
||||
<value>Default</value>
|
||||
</data>
|
||||
<data name="Confirm.Alias.Delete" xml:space="preserve">
|
||||
<value>Are You Sure You Wish To Delete {0}?</value>
|
||||
@@ -342,12 +339,6 @@
|
||||
<data name="HomePage.Text" xml:space="preserve">
|
||||
<value>Home Page:</value>
|
||||
</data>
|
||||
<data name="SmtpRelay.HelpText" xml:space="preserve">
|
||||
<value>Only specify this option if you have properly configured an SMTP Relay Service to route your outgoing mail. This option will send notifications from the user's email rather than from the Email Sender specified above.</value>
|
||||
</data>
|
||||
<data name="SmtpRelay.Text" xml:space="preserve">
|
||||
<value>Relay Configured?</value>
|
||||
</data>
|
||||
<data name="SiteMap.HelpText" xml:space="preserve">
|
||||
<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>
|
||||
@@ -409,7 +400,7 @@
|
||||
<value>Hybrid Enabled?</value>
|
||||
</data>
|
||||
<data name="Runtime.HelpText" xml:space="preserve">
|
||||
<value>The render mode for UI components which require interactivity</value>
|
||||
<value>The hosting model for UI components which require interactivity</value>
|
||||
</data>
|
||||
<data name="Runtime.Text" xml:space="preserve">
|
||||
<value>Interactivity:</value>
|
||||
@@ -504,4 +495,76 @@
|
||||
<data name="StartTlsWhenAvailable" xml:space="preserve">
|
||||
<value>Use TLS When Available</value>
|
||||
</data>
|
||||
<data name="AliasName.HelpText" xml:space="preserve">
|
||||
<value>A url for this site. This can include domain names (ie. domain.com), subdomains (ie. sub.domain.com) or virtual folders (ie. domain.com/folder).</value>
|
||||
</data>
|
||||
<data name="SiteGroupMembers.Text" xml:space="preserve">
|
||||
<value>Group:</value>
|
||||
</data>
|
||||
<data name="SiteGroupMembers.HelpText" xml:space="preserve">
|
||||
<value>The site groups in this tenant (database)</value>
|
||||
</data>
|
||||
<data name="Primary.Text" xml:space="preserve">
|
||||
<value>Primary?</value>
|
||||
</data>
|
||||
<data name="Primary.HelpText" xml:space="preserve">
|
||||
<value>Indicates if the selected member is the primary site of the site group</value>
|
||||
</data>
|
||||
<data name="GroupName.Text" xml:space="preserve">
|
||||
<value>Name:</value>
|
||||
</data>
|
||||
<data name="GroupName.HelpText" xml:space="preserve">
|
||||
<value>Name of the site group</value>
|
||||
</data>
|
||||
<data name="Primary" xml:space="preserve">
|
||||
<value>Primary</value>
|
||||
</data>
|
||||
<data name="Secondary" xml:space="preserve">
|
||||
<value>Secondary</value>
|
||||
</data>
|
||||
<data name="Compare" xml:space="preserve">
|
||||
<value>Compare</value>
|
||||
</data>
|
||||
<data name="Update" xml:space="preserve">
|
||||
<value>Update</value>
|
||||
</data>
|
||||
<data name="DeleteSiteGroupMember.Header" xml:space="preserve">
|
||||
<value>Delete Site Group Member</value>
|
||||
</data>
|
||||
<data name="Confirm.SiteGroupMember.Delete" xml:space="preserve">
|
||||
<value>Are You Sure You Wish To Delete This Member From The Site Group?</value>
|
||||
</data>
|
||||
<data name="Message.Required.GroupName" xml:space="preserve">
|
||||
<value>Site Group Name Is Required</value>
|
||||
</data>
|
||||
<data name="Message.Site.Synchronize" xml:space="preserve">
|
||||
<value>Site Submitted For Synchronization</value>
|
||||
</data>
|
||||
<data name="Site.Text" xml:space="preserve">
|
||||
<value>Members:</value>
|
||||
</data>
|
||||
<data name="Site.HelpText" xml:space="preserve">
|
||||
<value>The sites which are members of this site group</value>
|
||||
</data>
|
||||
<data name="Synchronized.Text" xml:space="preserve">
|
||||
<value>Synchronized:</value>
|
||||
</data>
|
||||
<data name="GroupType.Text" xml:space="preserve">
|
||||
<value>Type:</value>
|
||||
</data>
|
||||
<data name="GroupType.HelpText" xml:space="preserve">
|
||||
<value>Defines the specific behavior of the site group</value>
|
||||
</data>
|
||||
<data name="Synchronized.HelpText" xml:space="preserve">
|
||||
<value>The date/time when the site was last synchronized</value>
|
||||
</data>
|
||||
<data name="Synchronization" xml:space="preserve">
|
||||
<value>Synchronization</value>
|
||||
</data>
|
||||
<data name="Localization" xml:space="preserve">
|
||||
<value>Localization</value>
|
||||
</data>
|
||||
<data name="ChangeDetection" xml:space="preserve">
|
||||
<value>Change Detection</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -123,29 +123,14 @@
|
||||
<data name="SqlServer" xml:space="preserve">
|
||||
<value>SQL Server</value>
|
||||
</data>
|
||||
<data name="Container.Select" xml:space="preserve">
|
||||
<value>Select Container</value>
|
||||
</data>
|
||||
<data name="DefaultContainer.Text" xml:space="preserve">
|
||||
<value>Default Container: </value>
|
||||
</data>
|
||||
<data name="Theme.Select" xml:space="preserve">
|
||||
<value>Select Theme</value>
|
||||
</data>
|
||||
<data name="Aliases.HelpText" xml:space="preserve">
|
||||
<value>The urls for the site (comman delimited). This can include domain names (ie. domain.com), subdomains (ie. sub.domain.com) or a virtual folder (ie. domain.com/folder).</value>
|
||||
</data>
|
||||
<data name="DefaultContainer.HelpText" xml:space="preserve">
|
||||
<value>Select the default container for the site</value>
|
||||
<value>The primary url for the site. This can be a domain name (ie. domain.com), subdomain (ie. sub.domain.com) or a virtual folder (ie. domain.com/folder).</value>
|
||||
</data>
|
||||
<data name="Tenant.Text" xml:space="preserve">
|
||||
<value>Database: </value>
|
||||
</data>
|
||||
<data name="Aliases.Text" xml:space="preserve">
|
||||
<value>Urls: </value>
|
||||
</data>
|
||||
<data name="DefaultTheme.Text" xml:space="preserve">
|
||||
<value>Default Theme: </value>
|
||||
<value>Url: </value>
|
||||
</data>
|
||||
<data name="SiteTemplate.Select" xml:space="preserve">
|
||||
<value>Select Site Template</value>
|
||||
@@ -177,9 +162,6 @@
|
||||
<data name="Name.HelpText" xml:space="preserve">
|
||||
<value>Enter the name of the site</value>
|
||||
</data>
|
||||
<data name="DefaultTheme.HelpText" xml:space="preserve">
|
||||
<value>Select the default theme for the site</value>
|
||||
</data>
|
||||
<data name="SiteTemplate.HelpText" xml:space="preserve">
|
||||
<value>Select the site template</value>
|
||||
</data>
|
||||
@@ -222,12 +204,6 @@
|
||||
<data name="Error.Database.LoadConfig" xml:space="preserve">
|
||||
<value>Error loading Database Configuration Control</value>
|
||||
</data>
|
||||
<data name="RenderMode.HelpText" xml:space="preserve">
|
||||
<value>The default render mode for the site</value>
|
||||
</data>
|
||||
<data name="RenderMode.Text" xml:space="preserve">
|
||||
<value>Render Mode: </value>
|
||||
</data>
|
||||
<data name="ConnectionString.HelpText" xml:space="preserve">
|
||||
<value>Enter a complete connection string including all parameters and delimiters</value>
|
||||
</data>
|
||||
@@ -240,10 +216,4 @@
|
||||
<data name="EnterConnectionString" xml:space="preserve">
|
||||
<value>Enter Connection String</value>
|
||||
</data>
|
||||
<data name="Runtime.HelpText" xml:space="preserve">
|
||||
<value>The render mode for UI components which require interactivity</value>
|
||||
</data>
|
||||
<data name="Runtime.Text" xml:space="preserve">
|
||||
<value>Interactivity:</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -186,4 +186,10 @@
|
||||
<data name="Permissions.Heading" xml:space="preserve">
|
||||
<value>Permissions</value>
|
||||
</data>
|
||||
<data name="Fingerprint.Text" xml:space="preserve">
|
||||
<value>Fingerprint:</value>
|
||||
</data>
|
||||
<data name="Fingerprint.HelpText" xml:space="preserve">
|
||||
<value>A unique identifier for the theme's static resources. This value can be changed by clicking the Save option below (ie. cache busting).</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -148,7 +148,7 @@
|
||||
<value>You Cannot Perform A System Update In A Development Environment</value>
|
||||
</data>
|
||||
<data name="Disclaimer.Text" xml:space="preserve">
|
||||
<value>Please Note That The System Update Capability Is A Simplified Upgrade Process Intended For Small To Medium Sized Installations. For Larger Enterprise Installations You Will Want To Use A Manual Upgrade Process.</value>
|
||||
<value>Please Note That The System Update Capability Is A Simplified Upgrade Process Intended For Small To Medium Sized Installations. For Enterprise Installations And Microsoft Azure Installations You Will Want To Use A Manual Upgrade Process.</value>
|
||||
</data>
|
||||
<data name="Backup.Text" xml:space="preserve">
|
||||
<value>Backup Files?</value>
|
||||
|
||||
@@ -288,4 +288,10 @@
|
||||
<data name="Error.Passkey.Fail" xml:space="preserve">
|
||||
<value>Passkey Could Not Be Created</value>
|
||||
</data>
|
||||
<data name="CultureCode.Text" xml:space="preserve">
|
||||
<value>Language:</value>
|
||||
</data>
|
||||
<data name="CultureCode.HelpText" xml:space="preserve">
|
||||
<value>Your preferred language. Note that you will only be able to choose from languages supported on this site.</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -168,4 +168,10 @@
|
||||
<data name="Confirmed.HelpText" xml:space="preserve">
|
||||
<value>Indicates if the user's email is verified</value>
|
||||
</data>
|
||||
<data name="CultureCode.Text" xml:space="preserve">
|
||||
<value>Language:</value>
|
||||
</data>
|
||||
<data name="CultureCode.HelpText" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
</root>
|
||||
@@ -252,4 +252,10 @@
|
||||
<data name="Message.Logins.None" xml:space="preserve">
|
||||
<value>You Do Not Have Any External Logins For This Site</value>
|
||||
</data>
|
||||
<data name="CultureCode.Text" xml:space="preserve">
|
||||
<value>Language:</value>
|
||||
</data>
|
||||
<data name="CultureCode.HelpText" xml:space="preserve">
|
||||
<value>The user's preferred language. Note that you will only be able to choose from languages supported on this site.</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -217,7 +217,7 @@
|
||||
<value>Unique Characters:</value>
|
||||
</data>
|
||||
<data name="AllowSiteLogin.HelpText" xml:space="preserve">
|
||||
<value>Do you want to allow users to sign in using a username and password that is managed locally on this site? Note that you should only disable this option if you have already sucessfully configured an external login provider, or else you may lock yourself out of the site.</value>
|
||||
<value>Do you want to allow users to sign in using a username and password that is managed locally on this site? Note that you should only disable this option if you have already successfully configured an alternate login method, or else you may lock yourself out of the site.</value>
|
||||
</data>
|
||||
<data name="AllowSiteLogin.Text" xml:space="preserve">
|
||||
<value>Allow Local Login?</value>
|
||||
@@ -370,7 +370,7 @@
|
||||
<value>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.</value>
|
||||
</data>
|
||||
<data name="TwoFactor.Text" xml:space="preserve">
|
||||
<value>Two Factor Authentication?</value>
|
||||
<value>Use 2FA?</value>
|
||||
</data>
|
||||
<data name="RequireConfirmedEmail.HelpText" xml:space="preserve">
|
||||
<value>Do you want to require registered users to verify their email address before they are allowed to log in?</value>
|
||||
@@ -567,4 +567,10 @@
|
||||
<data name="Passkeys.HelpText" xml:space="preserve">
|
||||
<value>Do you want to allow users to login using passkeys (ie. passwordless authentication using WebAuthn/FIDO2)?</value>
|
||||
</data>
|
||||
<data name="LoginLink.Text" xml:space="preserve">
|
||||
<value>Allow Login Link?</value>
|
||||
</data>
|
||||
<data name="LoginLink.HelpText" xml:space="preserve">
|
||||
<value>Do you want to allow users to login using a time sensitive link sent by email?</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -129,11 +129,8 @@
|
||||
<data name="Import" xml:space="preserve">
|
||||
<value>Import</value>
|
||||
</data>
|
||||
<data name="Message.Import.Failure" xml:space="preserve">
|
||||
<value>User Import Failed. Please Review Your Event Log For More Detailed Information.</value>
|
||||
</data>
|
||||
<data name="Message.Import.Success" xml:space="preserve">
|
||||
<value>User Import Successful. {0} Users Imported.</value>
|
||||
<value>Your User Import Request Has Been Submitted And Will Be Executed Shortly. Please Be Patient.</value>
|
||||
</data>
|
||||
<data name="Message.Import.Validation" xml:space="preserve">
|
||||
<value>You Must Specify A User File For Import</value>
|
||||
|
||||
@@ -144,4 +144,10 @@
|
||||
<data name="Message.File.Restricted" xml:space="preserve">
|
||||
<value>Files With Extension Of {0} Are Restricted From Upload. Please Contact Your Administrator For More Information.</value>
|
||||
</data>
|
||||
<data name="File.MaxSize" xml:space="preserve">
|
||||
<value>Maximum upload file size: {0} MB</value>
|
||||
</data>
|
||||
<data name="Message.File.TooLarge" xml:space="preserve">
|
||||
<value>File(s) {0} exceed(s) the maximum upload size of {1} MB</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -123,4 +123,10 @@
|
||||
<data name="DynamicTokens.Text" xml:space="preserve">
|
||||
<value>Dynamic Tokens?</value>
|
||||
</data>
|
||||
<data name="Versions.HelpText" xml:space="preserve">
|
||||
<value>The number of content versions to preserve (note that zero means unlimited)</value>
|
||||
</data>
|
||||
<data name="Versions.Text" xml:space="preserve">
|
||||
<value>Versions:</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -204,6 +204,9 @@
|
||||
<data name="System Update" xml:space="preserve">
|
||||
<value>System Update</value>
|
||||
</data>
|
||||
<data name="Setting Management" xml:space="preserve">
|
||||
<value>Setting Management</value>
|
||||
</data>
|
||||
<data name="Download" xml:space="preserve">
|
||||
<value>Download</value>
|
||||
</data>
|
||||
@@ -480,4 +483,7 @@
|
||||
<data name="Installed" xml:space="preserve">
|
||||
<value>Installed</value>
|
||||
</data>
|
||||
<data name="Global Replace" xml:space="preserve">
|
||||
<value>Global Replace</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -174,9 +174,6 @@
|
||||
<data name="Title" xml:space="preserve">
|
||||
<value>Title:</value>
|
||||
</data>
|
||||
<data name="System.Update" xml:space="preserve">
|
||||
<value>Check For System Updates</value>
|
||||
</data>
|
||||
<data name="Visibility" xml:space="preserve">
|
||||
<value>Visibility:</value>
|
||||
</data>
|
||||
@@ -200,5 +197,11 @@
|
||||
</data>
|
||||
<data name="Module.CopyExisting" xml:space="preserve">
|
||||
<value>Copy Existing Module</value>
|
||||
</data>
|
||||
</data>
|
||||
<data name="Synchronize" xml:space="preserve">
|
||||
<value>Synchronize Site</value>
|
||||
</data>
|
||||
<data name="Copy" xml:space="preserve">
|
||||
<value>Copy Page</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -124,6 +124,9 @@
|
||||
<value>Module Type Is Invalid For {0}</value>
|
||||
</data>
|
||||
<data name="Error.Module.Exception" xml:space="preserve">
|
||||
<value>An Unexpected Error Has Occurred</value>
|
||||
<value>An Unexpected Error Has Occurred</value>
|
||||
</data>
|
||||
<data name="Error.Module.InvalidInjectedServices" xml:space="preserve">
|
||||
<value>Missing service(s): {0}. Please make sure they have been registered correctly.</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -14,8 +14,9 @@ namespace Oqtane.Services
|
||||
/// Set the localization cookie
|
||||
/// </summary>
|
||||
/// <param name="culture"></param>
|
||||
/// <param name="uiCulture"></param>
|
||||
/// <returns></returns>
|
||||
Task SetLocalizationCookieAsync(string culture);
|
||||
Task SetLocalizationCookieAsync(string culture, string uiCulture);
|
||||
}
|
||||
|
||||
[PrivateApi("Don't show in the documentation, as everything should use the Interface")]
|
||||
@@ -23,7 +24,7 @@ namespace Oqtane.Services
|
||||
{
|
||||
public LocalizationCookieService(HttpClient http, SiteState siteState) : base(http, siteState) { }
|
||||
|
||||
public Task SetLocalizationCookieAsync(string culture)
|
||||
public Task SetLocalizationCookieAsync(string culture, string uiCulture)
|
||||
{
|
||||
return Task.CompletedTask; // only used in server side rendering
|
||||
}
|
||||
|
||||
@@ -13,10 +13,16 @@ namespace Oqtane.Services
|
||||
public interface ILocalizationService
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns a collection of supported cultures
|
||||
/// Returns a collection of supported or installed cultures
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task<IEnumerable<Culture>> GetCulturesAsync(bool installed);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a collection of neutral cultures
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task<IEnumerable<Culture>> GetNeutralCulturesAsync();
|
||||
}
|
||||
|
||||
[PrivateApi("Don't show in the documentation, as everything should use the Interface")]
|
||||
@@ -26,6 +32,14 @@ namespace Oqtane.Services
|
||||
|
||||
private string Apiurl => CreateApiUrl("Localization");
|
||||
|
||||
public async Task<IEnumerable<Culture>> GetCulturesAsync(bool installed) => await GetJsonAsync<IEnumerable<Culture>>($"{Apiurl}?installed={installed}");
|
||||
public async Task<IEnumerable<Culture>> GetCulturesAsync(bool installed)
|
||||
{
|
||||
return await GetJsonAsync<IEnumerable<Culture>>($"{Apiurl}?installed={installed}");
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Culture>> GetNeutralCulturesAsync()
|
||||
{
|
||||
return await GetJsonAsync<IEnumerable<Culture>>($"{Apiurl}/neutral");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,6 +71,15 @@ namespace Oqtane.Services
|
||||
/// <param name="pageId"></param>
|
||||
/// <returns></returns>
|
||||
Task DeletePageAsync(int pageId);
|
||||
|
||||
/// <summary>
|
||||
/// Copies the modules from one page to another
|
||||
/// </summary>
|
||||
/// <param name="fromPageId"></param>
|
||||
/// <param name="toPageId"></param>
|
||||
/// <param name="usePagePermissions"></param>
|
||||
/// <returns></returns>
|
||||
Task CopyPageAsync(int fromPageId, int toPageId, bool usePagePermissions);
|
||||
}
|
||||
|
||||
[PrivateApi("Don't show in the documentation, as everything should use the Interface")]
|
||||
@@ -129,5 +138,10 @@ namespace Oqtane.Services
|
||||
{
|
||||
await DeleteAsync($"{Apiurl}/{pageId}");
|
||||
}
|
||||
|
||||
public async Task CopyPageAsync(int fromPageId, int toPageId, bool usePagePermissions)
|
||||
{
|
||||
await PostAsync($"{Apiurl}/{fromPageId}/{toPageId}/{usePagePermissions}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
104
Oqtane.Client/Services/SiteGroupMemberService.cs
Normal file
104
Oqtane.Client/Services/SiteGroupMemberService.cs
Normal file
@@ -0,0 +1,104 @@
|
||||
using Oqtane.Models;
|
||||
using System.Threading.Tasks;
|
||||
using System.Net.Http;
|
||||
using System.Collections.Generic;
|
||||
using Oqtane.Documentation;
|
||||
using Oqtane.Shared;
|
||||
using System.Linq;
|
||||
|
||||
namespace Oqtane.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Service to manage <see cref="Role"/>s on a <see cref="Site"/>
|
||||
/// </summary>
|
||||
public interface ISiteGroupMemberService
|
||||
{
|
||||
/// <summary>
|
||||
/// Get all <see cref="SiteGroupMember"/>s
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task<List<SiteGroupMember>> GetSiteGroupMembersAsync(int siteId, int siteGroupId);
|
||||
|
||||
/// <summary>
|
||||
/// Get one specific <see cref="SiteGroupMember"/>
|
||||
/// </summary>
|
||||
/// <param name="siteGroupMemberId">ID-reference of a <see cref="SiteGroupMember"/></param>
|
||||
/// <returns></returns>
|
||||
Task<SiteGroupMember> GetSiteGroupMemberAsync(int siteGroupMemberId);
|
||||
|
||||
/// <summary>
|
||||
/// Get one specific <see cref="SiteGroupMember"/>
|
||||
/// </summary>
|
||||
/// <param name="siteId">ID-reference of a <see cref="Site"/></param>
|
||||
/// <param name="siteGroupId">ID-reference of a <see cref="SiteGroup"/></param>
|
||||
/// <returns></returns>
|
||||
Task<SiteGroupMember> GetSiteGroupMemberAsync(int siteId, int siteGroupId);
|
||||
|
||||
/// <summary>
|
||||
/// Add / save a new <see cref="SiteGroupMember"/> to the database.
|
||||
/// </summary>
|
||||
/// <param name="siteGroupMember"></param>
|
||||
/// <returns></returns>
|
||||
Task<SiteGroupMember> AddSiteGroupMemberAsync(SiteGroupMember siteGroupMember);
|
||||
|
||||
/// <summary>
|
||||
/// Update a <see cref="SiteGroupMember"/> in the database.
|
||||
/// </summary>
|
||||
/// <param name="siteGroupMember"></param>
|
||||
/// <returns></returns>
|
||||
Task<SiteGroupMember> UpdateSiteGroupMemberAsync(SiteGroupMember siteGroupMember);
|
||||
|
||||
/// <summary>
|
||||
/// Delete a <see cref="SiteGroupMember"/> in the database.
|
||||
/// </summary>
|
||||
/// <param name="siteGroupMemberId">ID-reference of a <see cref="SiteGroupMember"/></param>
|
||||
/// <returns></returns>
|
||||
Task DeleteSiteGroupMemberAsync(int siteGroupMemberId);
|
||||
}
|
||||
|
||||
[PrivateApi("Don't show in the documentation, as everything should use the Interface")]
|
||||
public class SiteGroupMemberService : ServiceBase, ISiteGroupMemberService
|
||||
{
|
||||
public SiteGroupMemberService(HttpClient http, SiteState siteState) : base(http, siteState) { }
|
||||
|
||||
private string Apiurl => CreateApiUrl("SiteGroupMember");
|
||||
|
||||
public async Task<List<SiteGroupMember>> GetSiteGroupMembersAsync(int siteId, int siteGroupId)
|
||||
{
|
||||
return await GetJsonAsync<List<SiteGroupMember>>($"{Apiurl}?siteid={siteId}&groupid={siteGroupId}", Enumerable.Empty<SiteGroupMember>().ToList());
|
||||
}
|
||||
|
||||
public async Task<SiteGroupMember> GetSiteGroupMemberAsync(int siteGroupMemberId)
|
||||
{
|
||||
return await GetJsonAsync<SiteGroupMember>($"{Apiurl}/{siteGroupMemberId}");
|
||||
}
|
||||
|
||||
public async Task<SiteGroupMember> GetSiteGroupMemberAsync(int siteId, int siteGroupId)
|
||||
{
|
||||
var siteGroupMembers = await GetSiteGroupMembersAsync(siteId, siteGroupId);
|
||||
if (siteGroupMembers != null && siteGroupMembers.Count > 0)
|
||||
{
|
||||
return siteGroupMembers[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<SiteGroupMember> AddSiteGroupMemberAsync(SiteGroupMember siteGroupMember)
|
||||
{
|
||||
return await PostJsonAsync<SiteGroupMember>(Apiurl, siteGroupMember);
|
||||
}
|
||||
|
||||
public async Task<SiteGroupMember> UpdateSiteGroupMemberAsync(SiteGroupMember siteGroupMember)
|
||||
{
|
||||
return await PutJsonAsync<SiteGroupMember>($"{Apiurl}/{siteGroupMember.SiteGroupId}", siteGroupMember);
|
||||
}
|
||||
|
||||
public async Task DeleteSiteGroupMemberAsync(int siteGroupMemberId)
|
||||
{
|
||||
await DeleteAsync($"{Apiurl}/{siteGroupMemberId}");
|
||||
}
|
||||
}
|
||||
}
|
||||
94
Oqtane.Client/Services/SiteGroupService.cs
Normal file
94
Oqtane.Client/Services/SiteGroupService.cs
Normal file
@@ -0,0 +1,94 @@
|
||||
using Oqtane.Models;
|
||||
using System.Threading.Tasks;
|
||||
using System.Net.Http;
|
||||
using System.Collections.Generic;
|
||||
using Oqtane.Documentation;
|
||||
using Oqtane.Shared;
|
||||
using System.Linq;
|
||||
|
||||
namespace Oqtane.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Service to manage <see cref="Role"/>s on a <see cref="Site"/>
|
||||
/// </summary>
|
||||
public interface ISiteGroupService
|
||||
{
|
||||
/// <summary>
|
||||
/// Get all <see cref="SiteGroup"/>s
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task<List<SiteGroup>> GetSiteGroupsAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Get all <see cref="SiteGroup"/>s
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task<List<SiteGroup>> GetSiteGroupsAsync(int primarySiteId);
|
||||
|
||||
/// <summary>
|
||||
/// Get one specific <see cref="SiteGroup"/>
|
||||
/// </summary>
|
||||
/// <param name="siteGroupId">ID-reference of a <see cref="SiteGroup"/></param>
|
||||
/// <returns></returns>
|
||||
Task<SiteGroup> GetSiteGroupAsync(int siteGroupId);
|
||||
|
||||
/// <summary>
|
||||
/// Add / save a new <see cref="SiteGroup"/> to the database.
|
||||
/// </summary>
|
||||
/// <param name="group"></param>
|
||||
/// <returns></returns>
|
||||
Task<SiteGroup> AddSiteGroupAsync(SiteGroup siteGroup);
|
||||
|
||||
/// <summary>
|
||||
/// Update a <see cref="SiteGroup"/> in the database.
|
||||
/// </summary>
|
||||
/// <param name="group"></param>
|
||||
/// <returns></returns>
|
||||
Task<SiteGroup> UpdateSiteGroupAsync(SiteGroup siteGroup);
|
||||
|
||||
/// <summary>
|
||||
/// Delete a <see cref="SiteGroup"/> in the database.
|
||||
/// </summary>
|
||||
/// <param name="siteGroupId">ID-reference of a <see cref="SiteGroup"/></param>
|
||||
/// <returns></returns>
|
||||
Task DeleteSiteGroupAsync(int siteGroupId);
|
||||
}
|
||||
|
||||
[PrivateApi("Don't show in the documentation, as everything should use the Interface")]
|
||||
public class SiteGroupService : ServiceBase, ISiteGroupService
|
||||
{
|
||||
public SiteGroupService(HttpClient http, SiteState siteState) : base(http, siteState) { }
|
||||
|
||||
private string Apiurl => CreateApiUrl("SiteGroup");
|
||||
|
||||
public async Task<List<SiteGroup>> GetSiteGroupsAsync()
|
||||
{
|
||||
return await GetSiteGroupsAsync(-1);
|
||||
}
|
||||
|
||||
public async Task<List<SiteGroup>> GetSiteGroupsAsync(int primarySiteId)
|
||||
{
|
||||
return await GetJsonAsync<List<SiteGroup>>($"{Apiurl}?siteid={primarySiteId}", Enumerable.Empty<SiteGroup>().ToList());
|
||||
}
|
||||
|
||||
public async Task<SiteGroup> GetSiteGroupAsync(int siteGroupId)
|
||||
{
|
||||
return await GetJsonAsync<SiteGroup>($"{Apiurl}/{siteGroupId}");
|
||||
}
|
||||
|
||||
public async Task<SiteGroup> AddSiteGroupAsync(SiteGroup siteGroup)
|
||||
{
|
||||
return await PostJsonAsync<SiteGroup>(Apiurl, siteGroup);
|
||||
}
|
||||
|
||||
public async Task<SiteGroup> UpdateSiteGroupAsync(SiteGroup siteGroup)
|
||||
{
|
||||
return await PutJsonAsync<SiteGroup>($"{Apiurl}/{siteGroup.SiteGroupId}", siteGroup);
|
||||
}
|
||||
|
||||
public async Task DeleteSiteGroupAsync(int siteGroupId)
|
||||
{
|
||||
await DeleteAsync($"{Apiurl}/{siteGroupId}");
|
||||
}
|
||||
}
|
||||
}
|
||||
46
Oqtane.Client/Services/SiteTaskService.cs
Normal file
46
Oqtane.Client/Services/SiteTaskService.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using Oqtane.Models;
|
||||
using System.Threading.Tasks;
|
||||
using System.Net.Http;
|
||||
using Oqtane.Documentation;
|
||||
using Oqtane.Shared;
|
||||
|
||||
namespace Oqtane.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Service to manage tasks (<see cref="SiteTask"/>)
|
||||
/// </summary>
|
||||
public interface ISiteTaskService
|
||||
{
|
||||
/// <summary>
|
||||
/// Return a specific task
|
||||
/// </summary>
|
||||
/// <param name="siteTaskId"></param>
|
||||
/// <returns></returns>
|
||||
Task<SiteTask> GetSiteTaskAsync(int siteTaskId);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new task
|
||||
/// </summary>
|
||||
/// <param name="siteTask"></param>
|
||||
/// <returns></returns>
|
||||
Task<SiteTask> AddSiteTaskAsync(SiteTask siteTask);
|
||||
}
|
||||
|
||||
[PrivateApi("Don't show in the documentation, as everything should use the Interface")]
|
||||
public class SiteTaskService : ServiceBase, ISiteTaskService
|
||||
{
|
||||
public SiteTaskService(HttpClient http, SiteState siteState) : base(http, siteState) { }
|
||||
|
||||
private string Apiurl => CreateApiUrl("SiteTask");
|
||||
|
||||
public async Task<SiteTask> GetSiteTaskAsync(int siteTaskId)
|
||||
{
|
||||
return await GetJsonAsync<SiteTask>($"{Apiurl}/{siteTaskId}");
|
||||
}
|
||||
|
||||
public async Task<SiteTask> AddSiteTaskAsync(SiteTask siteTask)
|
||||
{
|
||||
return await PostJsonAsync<SiteTask>(Apiurl, siteTask);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -97,11 +97,18 @@ namespace Oqtane.Services
|
||||
Task<User> VerifyEmailAsync(User user, string token);
|
||||
|
||||
/// <summary>
|
||||
/// Trigger a forgot-password e-mail for this <see cref="User"/>.
|
||||
/// Trigger a forgot-password e-mail.
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
/// <param name="username"></param>
|
||||
/// <returns></returns>
|
||||
Task ForgotPasswordAsync(User user);
|
||||
Task<bool> ForgotPasswordAsync(string username);
|
||||
|
||||
/// <summary>
|
||||
/// Trigger a username reminder e-mail.
|
||||
/// </summary>
|
||||
/// <param name="email"></param>
|
||||
/// <returns></returns>
|
||||
Task<bool> ForgotUsernameAsync(string email);
|
||||
|
||||
/// <summary>
|
||||
/// Reset the password of this <see cref="User"/>
|
||||
@@ -154,15 +161,6 @@ namespace Oqtane.Services
|
||||
/// <returns></returns>
|
||||
Task<string> GetPasswordRequirementsAsync(int siteId);
|
||||
|
||||
/// <summary>
|
||||
/// Bulk import of users
|
||||
/// </summary>
|
||||
/// <param name="siteId">ID of a <see cref="Site"/></param>
|
||||
/// <param name="fileId">ID of a <see cref="File"/></param>
|
||||
/// <param name="notify">Indicates if new users should be notified by email</param>
|
||||
/// <returns></returns>
|
||||
Task<Dictionary<string, string>> ImportUsersAsync(int siteId, int fileId, bool notify);
|
||||
|
||||
/// <summary>
|
||||
/// Get passkeys for a user
|
||||
/// </summary>
|
||||
@@ -211,6 +209,13 @@ namespace Oqtane.Services
|
||||
/// <param name="key"></param>
|
||||
/// <returns></returns>
|
||||
Task DeleteLoginAsync(int userId, string provider, string key);
|
||||
|
||||
/// <summary>
|
||||
/// Send a login link
|
||||
/// </summary>
|
||||
/// <param name="email"></param>
|
||||
/// <returns></returns>
|
||||
Task<bool> SendLoginLinkAsync(string email, string returnurl);
|
||||
}
|
||||
|
||||
[PrivateApi("Don't show in the documentation, as everything should use the Interface")]
|
||||
@@ -275,9 +280,14 @@ namespace Oqtane.Services
|
||||
return await PostJsonAsync<User>($"{Apiurl}/verify?token={token}", user);
|
||||
}
|
||||
|
||||
public async Task ForgotPasswordAsync(User user)
|
||||
public async Task<bool> ForgotPasswordAsync(string username)
|
||||
{
|
||||
await PostJsonAsync($"{Apiurl}/forgot", user);
|
||||
return await GetJsonAsync<bool>($"{Apiurl}/forgotpassword/{WebUtility.UrlEncode(username)}");
|
||||
}
|
||||
|
||||
public async Task<bool> ForgotUsernameAsync(string email)
|
||||
{
|
||||
return await GetJsonAsync<bool>($"{Apiurl}/forgotusername/{WebUtility.UrlEncode(email)}");
|
||||
}
|
||||
|
||||
public async Task<User> ResetPasswordAsync(User user, string token)
|
||||
@@ -332,11 +342,6 @@ namespace Oqtane.Services
|
||||
return string.Format(passwordValidationCriteriaTemplate, minimumlength, uniquecharacters, digitRequirement, uppercaseRequirement, lowercaseRequirement, punctuationRequirement);
|
||||
}
|
||||
|
||||
public async Task<Dictionary<string, string>> ImportUsersAsync(int siteId, int fileId, bool notify)
|
||||
{
|
||||
return await PostJsonAsync<Dictionary<string, string>>($"{Apiurl}/import?siteid={siteId}&fileid={fileId}¬ify={notify}", null);
|
||||
}
|
||||
|
||||
public async Task<List<UserPasskey>> GetPasskeysAsync(int userId)
|
||||
{
|
||||
return await GetJsonAsync<List<UserPasskey>>($"{Apiurl}/passkey?id={userId}");
|
||||
@@ -366,5 +371,10 @@ namespace Oqtane.Services
|
||||
{
|
||||
await DeleteAsync($"{Apiurl}/login?id={userId}&provider={provider}&key={key}");
|
||||
}
|
||||
|
||||
public async Task<bool> SendLoginLinkAsync(string email, string returnurl)
|
||||
{
|
||||
return await GetJsonAsync<bool>($"{Apiurl}/loginlink/{WebUtility.UrlEncode(email)}/{WebUtility.UrlEncode(returnurl)}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,6 +97,7 @@
|
||||
Alias = PageState.Alias,
|
||||
Site = new Site
|
||||
{
|
||||
SiteId = PageState.Site.SiteId,
|
||||
DefaultContainerType = PageState.Site.DefaultContainerType,
|
||||
Settings = PageState.Site.Settings,
|
||||
Themes = PageState.Site.Themes
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
@inject ILogService logger
|
||||
@inject ISettingService SettingService
|
||||
@inject IJSRuntime jsRuntime
|
||||
@inject ISiteGroupService SiteGroupService
|
||||
@inject IServiceProvider ServiceProvider
|
||||
@inject ILogService LoggingService
|
||||
@inject IStringLocalizer<ControlPanelInteractive> Localizer
|
||||
@@ -34,6 +35,11 @@
|
||||
<button type="button" data-bs-dismiss="offcanvas" class="btn btn-primary col-12" @onclick=@(async () => Navigate("Admin"))>@Localizer["AdminDash"]</button>
|
||||
</div>
|
||||
</div>
|
||||
@if (_siteGroups.Any(item => (item.Type == SiteGroupTypes.Synchronization || item.Type == SiteGroupTypes.ChangeDetection) && item.PrimarySiteId == PageState.Site.SiteId))
|
||||
{
|
||||
<hr class="app-rule" />
|
||||
<button type="button" class="btn btn-secondary col-12 mt-1" @onclick="SynchronizeSite">@Localizer["Synchronize"]</button>
|
||||
}
|
||||
<hr class="app-rule" />
|
||||
}
|
||||
@if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.PermissionList))
|
||||
@@ -53,18 +59,29 @@
|
||||
<button type="button" class="btn btn-danger col ms-1" @onclick="ConfirmDelete">@SharedLocalizer["Delete"]</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row d-flex">
|
||||
<div class="col">
|
||||
@if (UserSecurity.ContainsRole(PageState.Page.PermissionList, PermissionNames.View, RoleNames.Everyone))
|
||||
{
|
||||
<button type="button" class="btn btn-secondary col-12" @onclick=@(async () => Publish("unpublish"))>@Localizer["Page.Unpublish"]</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button type="button" class="btn btn-secondary col-12" @onclick=@(async () => Publish("publish"))>@Localizer["Page.Publish"]</button>
|
||||
}
|
||||
@if (PageState.Page.UserId == null)
|
||||
{
|
||||
<div class="row d-flex mb-2">
|
||||
<div class="col">
|
||||
<button type="button" class="btn btn-secondary col-12" data-bs-dismiss="offcanvas" @onclick=@(async () => Navigate("Copy"))>@Localizer["Copy"]</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (!PageState.Page.Path.StartsWith("admin/"))
|
||||
{
|
||||
<div class="row d-flex">
|
||||
<div class="col">
|
||||
@if (UserSecurity.ContainsRole(PageState.Page.PermissionList, PermissionNames.View, RoleNames.Everyone))
|
||||
{
|
||||
<button type="button" class="btn btn-secondary col-12" @onclick=@(async () => Publish("unpublish"))>@Localizer["Page.Unpublish"]</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button type="button" class="btn btn-secondary col-12" @onclick=@(async () => Publish("publish"))>@Localizer["Page.Publish"]</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<hr class="app-rule" />
|
||||
|
||||
@if (_deleteConfirmation)
|
||||
@@ -149,7 +166,7 @@
|
||||
<option value="@p.PageId">@p.Name</option>
|
||||
}
|
||||
</select>
|
||||
<select class="form-select mt-1" @bind="@_moduleId">
|
||||
<select class="form-select mt-1" value="@_moduleId" @onchange="(e => PageModuleChanged(e))">
|
||||
<option value="-"><@Localizer["Module.Select"]></option>
|
||||
@foreach (Module module in _modules)
|
||||
{
|
||||
@@ -257,6 +274,7 @@
|
||||
private List<Page> _pages = new List<Page>();
|
||||
private List<Module> _modules = new List<Module>();
|
||||
private List<ThemeControl> _containers = new List<ThemeControl>();
|
||||
private List<SiteGroup> _siteGroups = new List<SiteGroup>();
|
||||
|
||||
private string _category = "Common";
|
||||
private string _pane = "";
|
||||
@@ -285,15 +303,16 @@
|
||||
_containers = ThemeService.GetContainerControls(PageState.Site.Themes, PageState.Page.ThemeType);
|
||||
_containerType = PageState.Site.DefaultContainerType;
|
||||
_allModuleDefinitions = await ModuleDefinitionService.GetModuleDefinitionsAsync(PageState.Page.SiteId);
|
||||
_moduleDefinitions = _allModuleDefinitions.Where(item => item.Categories.Contains(_category)).ToList();
|
||||
_moduleDefinitions = _allModuleDefinitions.Where(item => item.Categories.Split(',', StringSplitOptions.RemoveEmptyEntries).Contains(_category)).ToList();
|
||||
_categories = _allModuleDefinitions.SelectMany(m => m.Categories.Split(',', StringSplitOptions.RemoveEmptyEntries)).Distinct().Where(item => item != "Headless").ToList();
|
||||
_siteGroups = await SiteGroupService.GetSiteGroupsAsync(PageState.Site.SiteId);
|
||||
}
|
||||
}
|
||||
|
||||
private void CategoryChanged(ChangeEventArgs e)
|
||||
{
|
||||
_category = (string)e.Value;
|
||||
_moduleDefinitions = _allModuleDefinitions.Where(item => item.Categories.Contains(_category)).ToList();
|
||||
_moduleDefinitions = _allModuleDefinitions.Where(item => item.Categories.Split(',', StringSplitOptions.RemoveEmptyEntries).Contains(_category)).ToList();
|
||||
_moduleDefinitionName = "-";
|
||||
_message = "";
|
||||
}
|
||||
@@ -339,6 +358,20 @@
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task PageModuleChanged(ChangeEventArgs e)
|
||||
{
|
||||
_moduleId = (string)e.Value;
|
||||
if (_moduleId != "-")
|
||||
{
|
||||
_title = _modules.First(item => item.ModuleId == int.Parse(_moduleId)).Title;
|
||||
}
|
||||
else
|
||||
{
|
||||
_title = "";
|
||||
}
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task AddModule()
|
||||
{
|
||||
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, PageState.Page.PermissionList))
|
||||
@@ -490,6 +523,11 @@
|
||||
moduleId = int.Parse(PageState.Site.Settings[Constants.PageManagementModule]);
|
||||
NavigationManager.NavigateTo(Utilities.EditUrl(PageState.Alias.Path, "admin/pages", moduleId, location, $"id={PageState.Page.PageId}&returnurl={WebUtility.UrlEncode(PageState.Route.PathAndQuery)}"));
|
||||
break;
|
||||
case "Copy":
|
||||
// get page management moduleid
|
||||
moduleId = int.Parse(PageState.Site.Settings[Constants.PageManagementModule]);
|
||||
NavigationManager.NavigateTo(Utilities.EditUrl(PageState.Alias.Path, "admin/pages", moduleId, "Edit", $"id={PageState.Page.PageId}©=true&returnurl={WebUtility.UrlEncode(PageState.Route.PathAndQuery)}"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -631,4 +669,14 @@
|
||||
{
|
||||
_message = "";
|
||||
}
|
||||
|
||||
private async Task SynchronizeSite()
|
||||
{
|
||||
foreach (var group in _siteGroups.Where(item => (item.Type == SiteGroupTypes.Synchronization || item.Type == SiteGroupTypes.ChangeDetection) && item.PrimarySiteId == PageState.Site.SiteId))
|
||||
{
|
||||
group.Synchronize = true;
|
||||
await SiteGroupService.UpdateSiteGroupAsync(group);
|
||||
}
|
||||
NavigationManager.NavigateTo(Utilities.NavigateUrl(PageState.Alias.Path, PageState.Page.Path, ""), true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +83,10 @@
|
||||
get => "";
|
||||
set
|
||||
{
|
||||
_showBanner = bool.Parse(value);
|
||||
if (!bool.TryParse(value, out _showBanner))
|
||||
{
|
||||
_showBanner = false;
|
||||
}
|
||||
_togglePostback = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,22 +6,29 @@
|
||||
@inject ILocalizationCookieService LocalizationCookieService
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
@if (_supportedCultures?.Count() > 1)
|
||||
@if (PageState.Site.Languages.Count() > 1)
|
||||
{
|
||||
<div class="app-languages btn-group pe-1" role="group">
|
||||
<button id="btnCultures" type="button" class="btn @ButtonClass dropdown-toggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="oi oi-globe"></span>
|
||||
</button>
|
||||
<div class="dropdown-menu @MenuAlignment" aria-labelledby="btnCultures">
|
||||
@foreach (var culture in _supportedCultures)
|
||||
@foreach (var language in PageState.Site.Languages)
|
||||
{
|
||||
@if (PageState.RenderMode == RenderModes.Interactive)
|
||||
@if (_contentLocalization)
|
||||
{
|
||||
<a class="dropdown-item @(CultureInfo.CurrentUICulture.Name == culture.Name ? "active" : String.Empty)" href="#" @onclick="@(async e => await SetCultureAsync(culture.Name))" @onclick:preventDefault="true">@culture.DisplayName</a>
|
||||
<a class="dropdown-item @(PageState.Site.CultureCode == language.Code ? "active" : String.Empty)" href="@(PageState.Alias.Protocol + language.AliasName)" data-enhance-nav="false">@language.Name</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<a class="dropdown-item @(CultureInfo.CurrentUICulture.Name == culture.Name ? "active" : String.Empty)" href="@NavigateUrl(PageState.Page.Path, "culture=" + culture.Name)" data-enhance-nav="false">@culture.DisplayName</a>
|
||||
@if (PageState.RenderMode == RenderModes.Interactive)
|
||||
{
|
||||
<a class="dropdown-item @(CultureInfo.CurrentUICulture.Name == language.Code ? "active" : String.Empty)" href="#" @onclick="@(async e => await SetCultureAsync(language.Code))" @onclick:preventDefault="true">@language.Name</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<a class="dropdown-item @(CultureInfo.CurrentUICulture.Name == language.Code ? "active" : String.Empty)" href="@NavigateUrl(PageState.Page.Path, "culture=" + language.Code)" data-enhance-nav="false">@language.Name</a>
|
||||
}
|
||||
}
|
||||
}
|
||||
</div>
|
||||
@@ -29,7 +36,7 @@
|
||||
}
|
||||
|
||||
@code{
|
||||
private IEnumerable<Culture> _supportedCultures;
|
||||
private bool _contentLocalization;
|
||||
private string MenuAlignment = string.Empty;
|
||||
|
||||
[Parameter]
|
||||
@@ -41,14 +48,15 @@
|
||||
{
|
||||
MenuAlignment = DropdownAlignment.ToLower() == "right" ? "dropdown-menu-end" : string.Empty;
|
||||
|
||||
_supportedCultures = PageState.Languages.Select(l => new Culture { Name = l.Code, DisplayName = l.Name });
|
||||
// determine if site is using content localization
|
||||
_contentLocalization = PageState.Languages.Any(item => !string.IsNullOrEmpty(item.AliasName));
|
||||
|
||||
if (PageState.QueryString.ContainsKey("culture"))
|
||||
{
|
||||
var culture = PageState.QueryString["culture"];
|
||||
if (_supportedCultures.Any(item => item.Name == culture))
|
||||
if (PageState.Site.Languages.Any(item => item.Code == culture))
|
||||
{
|
||||
await LocalizationCookieService.SetLocalizationCookieAsync(culture);
|
||||
await LocalizationCookieService.SetLocalizationCookieAsync(PageState.Site.CultureCode, culture);
|
||||
}
|
||||
NavigationManager.NavigateTo(NavigationManager.Uri.Replace($"?culture={culture}", ""));
|
||||
}
|
||||
@@ -58,7 +66,7 @@
|
||||
{
|
||||
if (culture != CultureInfo.CurrentUICulture.Name)
|
||||
{
|
||||
var localizationCookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture));
|
||||
var localizationCookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(PageState.Site.CultureCode, culture));
|
||||
var interop = new Interop(JSRuntime);
|
||||
await interop.SetCookie(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, 360, true, "Lax");
|
||||
NavigationManager.NavigateTo(NavigationManager.Uri, true);
|
||||
|
||||
@@ -1,20 +1,36 @@
|
||||
@namespace Oqtane.Themes.Controls
|
||||
@namespace Oqtane.Themes.Controls
|
||||
|
||||
@switch (Orientation)
|
||||
@if (_menuType != null)
|
||||
{
|
||||
case "Horizontal":
|
||||
<MenuHorizontal/>
|
||||
break;
|
||||
default: // Vertical
|
||||
{
|
||||
<MenuVertical/>
|
||||
break;
|
||||
}
|
||||
<DynamicComponent Type="@_menuType" Parameters="@Attributes"></DynamicComponent>
|
||||
}
|
||||
|
||||
@code{
|
||||
|
||||
[Parameter]
|
||||
public string Orientation { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string MenuType { get; set; }
|
||||
|
||||
[Parameter(CaptureUnmatchedValues = true)]
|
||||
public Dictionary<string, object> Attributes { get; set; } = new Dictionary<string, object>();
|
||||
|
||||
private Type _menuType;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
if (string.IsNullOrEmpty(MenuType) && !string.IsNullOrEmpty(Orientation))
|
||||
{
|
||||
if (Orientation == "Horizontal")
|
||||
{
|
||||
MenuType = "Oqtane.Themes.Controls.MenuHorizontal, Oqtane.Client";
|
||||
}
|
||||
else
|
||||
{
|
||||
MenuType = "Oqtane.Themes.Controls.MenuVertical, Oqtane.Client";
|
||||
}
|
||||
}
|
||||
|
||||
_menuType = Type.GetType(MenuType);
|
||||
}
|
||||
}
|
||||
|
||||
44
Oqtane.Client/Themes/Controls/Theme/Register.razor
Normal file
44
Oqtane.Client/Themes/Controls/Theme/Register.razor
Normal file
@@ -0,0 +1,44 @@
|
||||
@namespace Oqtane.Themes.Controls
|
||||
@using System.Net
|
||||
@inherits ThemeControlBase
|
||||
@inject IStringLocalizer<Login> Localizer
|
||||
@inject ISettingService SettingService
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
|
||||
<a href="@_registerurl" class="@CssClass">@SharedLocalizer["Register"]</a>
|
||||
|
||||
@code
|
||||
{
|
||||
private string _returnurl;
|
||||
private string _registerurl;
|
||||
|
||||
[Parameter]
|
||||
public string CssClass { get; set; } = "btn btn-secondary";
|
||||
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
if (!PageState.QueryString.ContainsKey("returnurl"))
|
||||
{
|
||||
// remember current url
|
||||
_returnurl = WebUtility.UrlEncode(PageState.Route.PathAndQuery);
|
||||
}
|
||||
else
|
||||
{
|
||||
// use existing value
|
||||
_returnurl = PageState.QueryString["returnurl"];
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:RegisterUrl", "")))
|
||||
{
|
||||
_registerurl = SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:RegisterUrl", "");
|
||||
_registerurl += (!_registerurl.Contains("?") ? "?" : "&") + "returnurl=" + (_registerurl.Contains("://") ? WebUtility.UrlEncode(PageState.Route.RootUrl) + _returnurl : _returnurl);
|
||||
}
|
||||
else
|
||||
{
|
||||
_registerurl = NavigateUrl("register", "returnurl=" + _returnurl);
|
||||
}
|
||||
|
||||
Console.WriteLine($"Register URL: {_registerurl}");
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,8 @@
|
||||
|
||||
<main role="main">
|
||||
<nav class="navbar navbar-dark bg-primary fixed-top">
|
||||
<Logo UseSiteNameAsFallback="true" /><Menu Orientation="Horizontal" />
|
||||
<Logo UseSiteNameAsFallback="true" />
|
||||
<Menu MenuType="Oqtane.Themes.Controls.MenuHorizontal, Oqtane.Client" />
|
||||
<div class="controls ms-auto">
|
||||
<div class="controls-group">
|
||||
<Search CssClass="me-3 text-center bg-primary" />
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
@namespace Oqtane.UI
|
||||
@using System.Reflection
|
||||
@using Module = Oqtane.Models.Module
|
||||
@inject IServiceProvider ServiceProvider
|
||||
@inject SiteState ComponentSiteState
|
||||
@inject IStringLocalizer<ModuleInstance> Localizer
|
||||
@inject ILogService LoggingService
|
||||
@inject NavigationManager NavigationManager
|
||||
@inherits ErrorBoundary
|
||||
|
||||
<CascadingValue Value="@PageState" IsFixed="true">
|
||||
@@ -12,7 +16,7 @@
|
||||
{
|
||||
@if (!string.IsNullOrEmpty(_messageContent) && _messagePosition == "top")
|
||||
{
|
||||
<ModuleMessage Message="@_messageContent" Type="@_messageType" Parent="@this" Style="@_messageStyle" />
|
||||
<ModuleMessage @key="_messageVersionTop" Message="@_messageContent" Type="@_messageType" Parent="@this" Style="@_messageStyle" />
|
||||
}
|
||||
@DynamicComponent
|
||||
@if (_progressIndicator)
|
||||
@@ -21,7 +25,7 @@
|
||||
}
|
||||
@if (!string.IsNullOrEmpty(_messageContent) && _messagePosition == "bottom")
|
||||
{
|
||||
<ModuleMessage Message="@_messageContent" Type="@_messageType" Parent="@this" Style="@_messageStyle" />
|
||||
<ModuleMessage @key="_messageVersionBottom" Message="@_messageContent" Type="@_messageType" Parent="@this" Style="@_messageStyle" />
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,6 +52,8 @@
|
||||
private MessageStyle _messageStyle;
|
||||
private bool _progressIndicator = false;
|
||||
private string _error;
|
||||
private string _messageVersionTop = Guid.NewGuid().ToString();
|
||||
private string _messageVersionBottom = Guid.NewGuid().ToString();
|
||||
|
||||
[Parameter]
|
||||
public SiteState SiteState { get; set; }
|
||||
@@ -67,37 +73,50 @@
|
||||
{
|
||||
if (ShouldRender())
|
||||
{
|
||||
if (!string.IsNullOrEmpty(ModuleState.ModuleType))
|
||||
{
|
||||
ModuleType = Type.GetType(ModuleState.ModuleType);
|
||||
if (ModuleType != null)
|
||||
{
|
||||
// repopulate the SiteState service based on the values passed in the SiteState parameter (this is how state is marshalled across the render mode boundary)
|
||||
ComponentSiteState.Hydrate(SiteState);
|
||||
|
||||
DynamicComponent = builder =>
|
||||
{
|
||||
builder.OpenComponent(0, ModuleType);
|
||||
builder.AddAttribute(1, "RenderModeBoundary", this);
|
||||
builder.CloseComponent();
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
// module does not exist with typename specified
|
||||
_messageContent = string.Format(Localizer["Error.Module.InvalidName"], Utilities.GetTypeNameLastSegment(ModuleState.ModuleType, 0));
|
||||
_messageType = MessageType.Error;
|
||||
_messagePosition = "top";
|
||||
_messageStyle = MessageStyle.Alert;
|
||||
}
|
||||
}
|
||||
else
|
||||
if (string.IsNullOrEmpty(ModuleState.ModuleType))
|
||||
{
|
||||
_messageContent = string.Format(Localizer["Error.Module.InvalidType"], ModuleState.ModuleDefinitionName);
|
||||
_messageType = MessageType.Error;
|
||||
_messagePosition = "top";
|
||||
_messageStyle = MessageStyle.Alert;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
ModuleType = Type.GetType(ModuleState.ModuleType);
|
||||
var moduleName = Utilities.GetTypeNameLastSegment(ModuleState.ModuleType, 0);
|
||||
if (ModuleType == null)
|
||||
{
|
||||
// module does not exist with typename specified
|
||||
_messageContent = string.Format(Localizer["Error.Module.InvalidName"], moduleName);
|
||||
_messageType = MessageType.Error;
|
||||
_messagePosition = "top";
|
||||
_messageStyle = MessageStyle.Alert;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
//only validate the services injection in development environment
|
||||
if (NavigationManager.BaseUri.Contains("localhost:") && !ValidateModuleTypeInjectedServices(ModuleType, out IList<string> missingServices))
|
||||
{
|
||||
// module type is not valid for instantiation
|
||||
_messageContent = string.Format(Localizer["Error.Module.InvalidInjectedServices"], string.Join(",", missingServices));
|
||||
_messageType = MessageType.Error;
|
||||
_messagePosition = "top";
|
||||
_messageStyle = MessageStyle.Alert;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// repopulate the SiteState service based on the values passed in the SiteState parameter (this is how state is marshalled across the render mode boundary)
|
||||
ComponentSiteState.Hydrate(SiteState);
|
||||
|
||||
DynamicComponent = builder =>
|
||||
{
|
||||
builder.OpenComponent(0, ModuleType);
|
||||
builder.AddAttribute(1, "RenderModeBoundary", this);
|
||||
builder.CloseComponent();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,6 +145,18 @@
|
||||
_messageStyle = style;
|
||||
_progressIndicator = false;
|
||||
|
||||
if (style == MessageStyle.Toast && !string.IsNullOrEmpty(_messageContent))
|
||||
{
|
||||
if (_messagePosition == "top")
|
||||
{
|
||||
_messageVersionTop = Guid.NewGuid().ToString();
|
||||
}
|
||||
else if (_messagePosition == "bottom")
|
||||
{
|
||||
_messageVersionBottom = Guid.NewGuid().ToString();
|
||||
}
|
||||
}
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
@@ -165,4 +196,26 @@
|
||||
_error = "";
|
||||
base.Recover();
|
||||
}
|
||||
|
||||
private bool ValidateModuleTypeInjectedServices(Type moduleType, out IList<string> missingServices)
|
||||
{
|
||||
missingServices = new List<string>();
|
||||
|
||||
var properties = Utilities.GetPropertiesIncludingInherited(moduleType, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||
foreach(var property in properties)
|
||||
{
|
||||
var injectAttribute = property.GetCustomAttribute(typeof(InjectAttribute));
|
||||
if (injectAttribute != null)
|
||||
{
|
||||
var serviceType = property.PropertyType;
|
||||
var service = ServiceProvider.GetService(serviceType);
|
||||
if (serviceType != null && service == null)
|
||||
{
|
||||
missingServices.Add(Utilities.GetTypeNameLastSegment(serviceType.FullName, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return !missingServices.Any();
|
||||
}
|
||||
}
|
||||
@@ -158,11 +158,17 @@
|
||||
|
||||
// verify user is authenticated for current site
|
||||
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
|
||||
if (authState.User.Identity.IsAuthenticated && authState.User.Claims.Any(item => item.Type == Constants.SiteKeyClaimType && item.Value == SiteState.Alias.SiteKey))
|
||||
if (authState.User.IsAuthenticated() && authState.User.SiteKey() == SiteState.Alias.SiteKey)
|
||||
{
|
||||
// get user
|
||||
var userid = int.Parse(authState.User.Claims.First(item => item.Type == ClaimTypes.NameIdentifier).Value);
|
||||
user = await UserService.GetUserAsync(userid, SiteState.Alias.SiteId);
|
||||
if (PageState == null || PageState.User == null || PageState.User.UserId != authState.User.UserId())
|
||||
{
|
||||
// get user
|
||||
user = await UserService.GetUserAsync(authState.User.UserId(), SiteState.Alias.SiteId);
|
||||
}
|
||||
else
|
||||
{
|
||||
user = PageState.User;
|
||||
}
|
||||
if (user != null)
|
||||
{
|
||||
user.IsAuthenticated = authState.User.Identity.IsAuthenticated;
|
||||
@@ -227,15 +233,8 @@
|
||||
|
||||
if (page == null && route.PagePath == "") // naked path refers to site home page
|
||||
{
|
||||
if (site.HomePageId != null)
|
||||
{
|
||||
page = site.Pages.FirstOrDefault(item => item.PageId == site.HomePageId);
|
||||
}
|
||||
if (page == null)
|
||||
{
|
||||
// fallback to use the first page in the collection
|
||||
page = site.Pages.FirstOrDefault();
|
||||
}
|
||||
// fallback to use the first page in the collection
|
||||
page = site.Pages.FirstOrDefault();
|
||||
}
|
||||
if (page == null)
|
||||
{
|
||||
@@ -626,11 +625,11 @@
|
||||
{
|
||||
if (resource.ResourceType == ResourceType.Stylesheet || resource.Level != ResourceLevel.Site)
|
||||
{
|
||||
if (resource.Url.StartsWith("~"))
|
||||
if (!string.IsNullOrEmpty(resource.Url) && resource.Url.StartsWith("~"))
|
||||
{
|
||||
resource.Url = resource.Url.Replace("~", "/" + type + "/" + name + "/").Replace("//", "/");
|
||||
}
|
||||
if (!resource.Url.Contains("://") && alias.BaseUrl != "" && !resource.Url.StartsWith(alias.BaseUrl))
|
||||
if (!string.IsNullOrEmpty(resource.Url) && !resource.Url.Contains("://") && alias.BaseUrl != "" && !resource.Url.StartsWith(alias.BaseUrl))
|
||||
{
|
||||
resource.Url = alias.BaseUrl + resource.Url;
|
||||
}
|
||||
|
||||
@@ -227,38 +227,4 @@
|
||||
}
|
||||
return stylesheets;
|
||||
}
|
||||
|
||||
private string ManageScripts(List<Resource> resources, Alias alias)
|
||||
{
|
||||
var scripts = "";
|
||||
if (resources != null)
|
||||
{
|
||||
foreach (var resource in resources.Where(item => item.ResourceType == ResourceType.Script && item.Location == ResourceLocation.Head))
|
||||
{
|
||||
var script = CreateScript(resource, alias);
|
||||
if (!scripts.Contains(script, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
scripts += script + Environment.NewLine;
|
||||
}
|
||||
}
|
||||
}
|
||||
return scripts;
|
||||
}
|
||||
|
||||
private string CreateScript(Resource resource, Alias alias)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(resource.Url))
|
||||
{
|
||||
var url = (resource.Url.Contains("://")) ? resource.Url : alias.BaseUrl + resource.Url;
|
||||
return "<script src=\"" + url + "\"" +
|
||||
((!string.IsNullOrEmpty(resource.Integrity)) ? " integrity=\"" + resource.Integrity + "\"" : "") +
|
||||
((!string.IsNullOrEmpty(resource.CrossOrigin)) ? " crossorigin=\"" + resource.CrossOrigin + "\"" : "") +
|
||||
"></script>";
|
||||
}
|
||||
else
|
||||
{
|
||||
// inline script
|
||||
return "<script>" + resource.Content + "</script>";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
||||
@using Microsoft.Extensions.Localization
|
||||
@using Microsoft.JSInterop
|
||||
@using Microsoft.AspNetCore.Authorization
|
||||
@using static Microsoft.AspNetCore.Components.Web.RenderMode
|
||||
|
||||
@using Oqtane.Client
|
||||
@@ -27,3 +28,4 @@
|
||||
@using Oqtane.Enums
|
||||
@using Oqtane.Installer
|
||||
@using Oqtane.Interfaces
|
||||
@using Oqtane.Extensions
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<ApplicationId>com.oqtane.maui</ApplicationId>
|
||||
|
||||
<!-- Versions -->
|
||||
<ApplicationDisplayVersion>10.0.0</ApplicationDisplayVersion>
|
||||
<ApplicationDisplayVersion>10.1.2</ApplicationDisplayVersion>
|
||||
<ApplicationVersion>1</ApplicationVersion>
|
||||
|
||||
<!-- To develop, package, and publish an app to the Microsoft Store, see: https://aka.ms/MauiTemplateUnpackaged -->
|
||||
@@ -54,11 +54,11 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="10.0.0" />
|
||||
<PackageReference Include="System.Net.Http.Json" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="10.0.5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="10.0.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="10.0.5" />
|
||||
<PackageReference Include="System.Net.Http.Json" Version="10.0.5" />
|
||||
<PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiVersion)" />
|
||||
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="$(MauiVersion)" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebView.Maui" Version="$(MauiVersion)" />
|
||||
|
||||
@@ -273,6 +273,11 @@ app {
|
||||
min-height: 250px;
|
||||
}
|
||||
|
||||
.app-editor-resizable {
|
||||
resize: vertical;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.app-logo .navbar-brand {
|
||||
padding: 5px 20px 5px 20px;
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ Oqtane.Interop = {
|
||||
}
|
||||
},
|
||||
includeScript: function (id, src, integrity, crossorigin, type, content, location, dataAttributes) {
|
||||
var script;
|
||||
var script = null;
|
||||
if (src !== "") {
|
||||
script = document.querySelector("script[src=\"" + CSS.escape(src) + "\"]");
|
||||
}
|
||||
@@ -140,7 +140,7 @@ Oqtane.Interop = {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (script !== null) {
|
||||
if (script instanceof HTMLScriptElement) {
|
||||
script.remove();
|
||||
script = null;
|
||||
}
|
||||
@@ -516,5 +516,17 @@ Oqtane.Interop = {
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
createCredential: async function (optionsResponse) {
|
||||
const optionsJson = JSON.parse(optionsResponse);
|
||||
const options = PublicKeyCredential.parseCreationOptionsFromJSON(optionsJson);
|
||||
const credential = await navigator.credentials.create({ publicKey: options });
|
||||
return JSON.stringify(credential);
|
||||
},
|
||||
requestCredential: async function (optionsResponse) {
|
||||
const optionsJson = JSON.parse(optionsResponse);
|
||||
const options = PublicKeyCredential.parseRequestOptionsFromJSON(optionsJson);
|
||||
const credential = await navigator.credentials.get({ publicKey: options, undefined });
|
||||
return JSON.stringify(credential);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<package>
|
||||
<metadata>
|
||||
<id>Oqtane.Client</id>
|
||||
<version>10.0.0</version>
|
||||
<version>10.1.2</version>
|
||||
<authors>Shaun Walker</authors>
|
||||
<owners>.NET Foundation</owners>
|
||||
<title>Oqtane Framework</title>
|
||||
@@ -12,18 +12,18 @@
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<license type="expression">MIT</license>
|
||||
<projectUrl>https://github.com/oqtane/oqtane.framework</projectUrl>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.0</releaseNotes>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v10.1.2</releaseNotes>
|
||||
<readme>readme.md</readme>
|
||||
<icon>icon.png</icon>
|
||||
<tags>oqtane</tags>
|
||||
<dependencies>
|
||||
<group targetFramework="net10.0">
|
||||
<dependency id="Oqtane.Shared" version="10.0.0" exclude="Build,Analyzers" />
|
||||
<dependency id="Microsoft.AspNetCore.Components.WebAssembly" version="10.0.0" exclude="Build,Analyzers" />
|
||||
<dependency id="Microsoft.AspNetCore.Components.WebAssembly.Authentication" version="10.0.0" exclude="Build,Analyzers" />
|
||||
<dependency id="Microsoft.Extensions.Localization" version="10.0.0" exclude="Build,Analyzers" />
|
||||
<dependency id="Microsoft.Extensions.Http" version="10.0.0" exclude="Build,Analyzers" />
|
||||
<dependency id="Radzen.Blazor" version="8.3.0" exclude="Build,Analyzers" />
|
||||
<dependency id="Oqtane.Shared" version="10.1.2" exclude="Build,Analyzers" />
|
||||
<dependency id="Microsoft.AspNetCore.Components.WebAssembly" version="10.0.5" exclude="Build,Analyzers" />
|
||||
<dependency id="Microsoft.AspNetCore.Components.WebAssembly.Authentication" version="10.0.5" exclude="Build,Analyzers" />
|
||||
<dependency id="Microsoft.Extensions.Localization" version="10.0.5" exclude="Build,Analyzers" />
|
||||
<dependency id="Microsoft.Extensions.Http" version="10.0.5" exclude="Build,Analyzers" />
|
||||
<dependency id="Radzen.Blazor" version="10.0.6" exclude="Build,Analyzers" />
|
||||
</group>
|
||||
</dependencies>
|
||||
</metadata>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<package>
|
||||
<metadata>
|
||||
<id>Oqtane.Framework</id>
|
||||
<version>10.0.0</version>
|
||||
<version>10.1.2</version>
|
||||
<authors>Shaun Walker</authors>
|
||||
<owners>.NET Foundation</owners>
|
||||
<title>Oqtane Framework</title>
|
||||
@@ -11,8 +11,8 @@
|
||||
<copyright>.NET Foundation</copyright>
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<license type="expression">MIT</license>
|
||||
<projectUrl>https://github.com/oqtane/oqtane.framework/releases/download/v10.0.0/Oqtane.Framework.10.0.0.Upgrade.zip</projectUrl>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.0</releaseNotes>
|
||||
<projectUrl>https://github.com/oqtane/oqtane.framework/releases/download/v10.1.2/Oqtane.Framework.10.1.2.Upgrade.zip</projectUrl>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v10.1.2</releaseNotes>
|
||||
<readme>readme.md</readme>
|
||||
<icon>icon.png</icon>
|
||||
<tags>oqtane framework</tags>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user