Compare commits
405 Commits
Author | SHA1 | Date | |
---|---|---|---|
43627d4bb8 | |||
5d7b276cd1 | |||
23597eb997 | |||
b6948367f8 | |||
aaaf5683a5 | |||
92aa2236c0 | |||
d2592f72d6 | |||
27120d6cc9 | |||
a2669d35c3 | |||
574164081b | |||
00c2f8dcd8 | |||
0202bf60e5 | |||
16436a171b | |||
4cf4e0eabd | |||
5edb98dfb4 | |||
899bf22e15 | |||
9a33167a6c | |||
2dc068aa21 | |||
0f2aa4d2e1 | |||
a699f5c7bc | |||
ab807de3c0 | |||
68514bcb36 | |||
422bf8da59 | |||
de92dc93dd | |||
5a91b143b6 | |||
c745e85706 | |||
0f698e0c50 | |||
cd16d77bf0 | |||
fdbdd0ef4c | |||
71485f4a82 | |||
013056a6e5 | |||
263498fbdc | |||
f46ac2c007 | |||
c23841b6db | |||
18b1b5fca5 | |||
6707ac2697 | |||
d85a2fc8ce | |||
a8997e8f17 | |||
c8a22d9537 | |||
910669f786 | |||
89312c6796 | |||
a6aa96fdb0 | |||
7deb0b06af | |||
002f8117cb | |||
0dfdb12431 | |||
22e3161a9b | |||
b0c8203b24 | |||
5ee1731c92 | |||
06e8d3b660 | |||
f09709aedb | |||
598d5decac | |||
7832a6053e | |||
588748230e | |||
8668165c72 | |||
a967332f89 | |||
6719d242bd | |||
3565185808 | |||
ce51262197 | |||
992a786c2b | |||
038df95aa0 | |||
4ebd660de2 | |||
a9ea41a488 | |||
196594b490 | |||
1516d5af10 | |||
ffcd1595a9 | |||
42e5c6e111 | |||
8a9651dc50 | |||
4be2f4f2d9 | |||
369bf7a235 | |||
136545b404 | |||
73ea17ae0f | |||
23010acef4 | |||
4f74962ce2 | |||
7f978c7845 | |||
731fd46ea2 | |||
859759d691 | |||
5e9567158f | |||
51d244f3aa | |||
4c5a07edd5 | |||
8113a754a1 | |||
3cd40c6195 | |||
56cfb2ce06 | |||
72087823ac | |||
bcf7866fe2 | |||
b64772e484 | |||
088d665942 | |||
7d6c10befb | |||
f88e3d04b8 | |||
f57676a22b | |||
8618cb62e4 | |||
c31c88ed1f | |||
6022acd21f | |||
b3071b9272 | |||
ec6a6d6653 | |||
52f552b4de | |||
2643d3396b | |||
62d59a09cf | |||
a68ff8a4f0 | |||
93d4bfcd7a | |||
5fb80c1a7b | |||
04b38444ce | |||
ca3df02002 | |||
d952c33fab | |||
93bc1cd5af | |||
0e5b370ee8 | |||
f4fd4e28c9 | |||
ec8433eb45 | |||
0d4a40e9bb | |||
c3668f4179 | |||
3adb7ecb1c | |||
aa5b84a214 | |||
3d83fccbf1 | |||
4c4255be6b | |||
ed6054b082 | |||
f60f7a4dc1 | |||
998dc95cb2 | |||
12f5d7b846 | |||
906ae0a43e | |||
485b774876 | |||
3121cf5b75 | |||
ce7570dae2 | |||
b5ea0dfbc7 | |||
dd0f8f4772 | |||
9d0ab34274 | |||
e6038be6f7 | |||
bd2153a0ed | |||
e526deac20 | |||
b65f165dcf | |||
f9fbe5adc2 | |||
d1e73571a1 | |||
1047058676 | |||
29a1e77da8 | |||
290547e482 | |||
3df45ca20f | |||
cc06258484 | |||
2c262d0655 | |||
1875e1e158 | |||
1c95967b31 | |||
2ae98929de | |||
6a1dd38cbb | |||
c458a77d27 | |||
352c23f389 | |||
6c5a1dc2e1 | |||
004ff1e91d | |||
f7de4c567b | |||
b2ad1010ac | |||
660e164ff8 | |||
eb6dc80b50 | |||
a9882cc96a | |||
fb5a2ce178 | |||
d441b31dc7 | |||
d4239fe7e0 | |||
6a98999105 | |||
5caa1fe7d4 | |||
ef33bdb65e | |||
14a463382b | |||
1ad79874c8 | |||
1a61a58d28 | |||
3e3c973679 | |||
9dede84d20 | |||
e78b11cf62 | |||
341ca5a330 | |||
e5d8c02def | |||
947aa08c42 | |||
9a04d436bd | |||
be0754f568 | |||
93c4bbc0d1 | |||
b98535810b | |||
393cf8da1f | |||
ea2846973a | |||
3398c1cbfe | |||
39c79ea68e | |||
66900f4a32 | |||
df71dd14f7 | |||
8113ca3069 | |||
d468e675c2 | |||
1e84cedf82 | |||
7f4087e3de | |||
facd3c8956 | |||
3e50deecb7 | |||
628c504f84 | |||
e1ada78c1f | |||
09fa1e365c | |||
28b6b03d06 | |||
a4395b62ff | |||
4511acf273 | |||
fde53a2d83 | |||
f6cd04fdb8 | |||
a5eede6c7a | |||
2e83817c83 | |||
82aea40ae4 | |||
1b289eae24 | |||
81420b2c88 | |||
775731b745 | |||
489e7d4a67 | |||
b6508764d8 | |||
6dedd87305 | |||
89fa29b310 | |||
be5df9c22a | |||
db17739716 | |||
1439362a5e | |||
65c1b04772 | |||
83d30ebdc4 | |||
b7928a5255 | |||
a83ff9253d | |||
8cdcdaf6d9 | |||
d208edf153 | |||
8ada4765b1 | |||
9d3a808c24 | |||
828eb80266 | |||
0639f6c1d1 | |||
49b971280f | |||
3682a1010d | |||
f8b58866dc | |||
5d8d815d84 | |||
67743c7597 | |||
b33cc90447 | |||
78177f7890 | |||
3c417bfa99 | |||
f2c8d80ff8 | |||
8c1eb1e19f | |||
eb7188e81b | |||
f352fc5c67 | |||
ac313722f9 | |||
64c7f1962c | |||
c83f994b21 | |||
10f38a72f7 | |||
da35434f58 | |||
c79409e094 | |||
355ce00968 | |||
56c832f3ba | |||
40abb2720e | |||
9b6051afee | |||
dcf6f26792 | |||
85734c1146 | |||
c3fb8fcb6e | |||
013bbc1638 | |||
b0669a3b60 | |||
6f5da1ce7c | |||
3351732a2f | |||
d4d4034ecd | |||
b45e2742c3 | |||
42eba290e4 | |||
32d1e08b57 | |||
7b7d19da7c | |||
f78e400918 | |||
be4e9bf7e9 | |||
48f2079f88 | |||
36ad1ceef2 | |||
1f2e2148d5 | |||
76f3d345f9 | |||
f2c854b53a | |||
e2d336d90b | |||
c74065ff26 | |||
b6d97dc5d5 | |||
1c1c26948a | |||
d954e3ffb7 | |||
d9759a95eb | |||
d196402dd0 | |||
0f2b5531de | |||
26e4398905 | |||
06995f22fe | |||
caa2073d48 | |||
d9466fe4bc | |||
72e623a3a7 | |||
69bc06685f | |||
4d4a7bfd0d | |||
044cee30a5 | |||
3c0c5aed60 | |||
e194971727 | |||
bbe85def23 | |||
9c2d53f2ae | |||
1cf36e2156 | |||
ce96e309af | |||
9ea7dc8b3c | |||
282242efd2 | |||
351ba22d64 | |||
2916625747 | |||
744dbeb7a3 | |||
fbb3c309ee | |||
473c265bac | |||
01b06c2c3c | |||
8b1f95c743 | |||
ff5dbec579 | |||
9620c5a98f | |||
7e849a2e95 | |||
229aed306e | |||
d718969cbd | |||
8593f4c3a9 | |||
d4f71d5026 | |||
a683a5f206 | |||
7917cc3eb5 | |||
6cd7ca755e | |||
fb7dfdc800 | |||
e096af320f | |||
65bff8f511 | |||
ca19d8a842 | |||
cde08c64ce | |||
3d6d7b7cb9 | |||
a56ef6f398 | |||
5a38b6614d | |||
3d5c44e8aa | |||
46a68bd5e7 | |||
c77ded51a9 | |||
59bd9fdc22 | |||
b81941394c | |||
197d5ca1f2 | |||
97eb986fa6 | |||
aba3110e31 | |||
51c27be236 | |||
592255284f | |||
82fd41dd4c | |||
4ae8df9652 | |||
759b19e444 | |||
36f705e46f | |||
1f0347682e | |||
1aee385679 | |||
5dd8191692 | |||
b6422f9b80 | |||
a6c2c9c92f | |||
247c573a6e | |||
aa435d6e94 | |||
f6858c221b | |||
437aa4510b | |||
430572fb32 | |||
9f0b755d6f | |||
66acb55a57 | |||
f936d4c36e | |||
c3ddb8df56 | |||
81ce920e69 | |||
3cb875d139 | |||
fdb217d5c6 | |||
085cae3b5f | |||
e2f99a1554 | |||
aee0c27da7 | |||
accbf4ad8b | |||
0ac1901f6b | |||
c0a0deea78 | |||
e3f099441c | |||
840dd25cd1 | |||
a493969f9b | |||
cca42a10a1 | |||
2f4aa98c3c | |||
175cb9588c | |||
a8976e7559 | |||
b663528fb0 | |||
1a2ad55677 | |||
513d2a88c0 | |||
57ef4c0396 | |||
e9599ca2f4 | |||
2fc6dbc222 | |||
225933c442 | |||
36e2f048d7 | |||
0e158bce59 | |||
202201fd31 | |||
4f8c928f44 | |||
ada062cf00 | |||
32dc12912a | |||
671d21adf4 | |||
4e3f8e4b67 | |||
586bb62073 | |||
151bf83ab1 | |||
8c618edb5a | |||
6d6b0cf8c9 | |||
c610608890 | |||
28da61daab | |||
cbfc90f60b | |||
fec92959d4 | |||
bb00d81eba | |||
bb55644c06 | |||
16215847cd | |||
8ee9aed817 | |||
515c6402b9 | |||
d1b94ec203 | |||
a037d9167e | |||
3054d33e62 | |||
6651e641e1 | |||
35f873a342 | |||
2d03ff38a1 | |||
44a3db417b | |||
f9ca702a12 | |||
4073ff38eb | |||
f0e2c9f1b6 | |||
cf040f51b5 | |||
dcf919fb36 | |||
aa19b81a68 | |||
db8d77365c | |||
280eaea84a | |||
340ef46469 | |||
a5f8651941 | |||
8a18ee548e | |||
ef791aa22a | |||
4bdf2e1cc0 | |||
ffa0ca9379 | |||
d3b3d46fc1 | |||
7e7dd8efa9 | |||
b4506f1133 | |||
7350c79113 | |||
5f7b60d3f4 | |||
266495a611 | |||
f5b4a7e77b | |||
51ed0f6487 | |||
bd70def18a | |||
93a9cf3b31 | |||
751287999f |
11
.gitignore
vendored
11
.gitignore
vendored
@ -22,10 +22,17 @@ Oqtane.Server/Packages
|
||||
Oqtane.Server/wwwroot/Content
|
||||
Oqtane.Server/wwwroot/Packages/*.log
|
||||
|
||||
Oqtane.Server/wwwroot/Modules
|
||||
Oqtane.Server/wwwroot/_content/*
|
||||
!Oqtane.Server/wwwroot/_content/Placeholder.txt
|
||||
|
||||
Oqtane.Server/wwwroot/Modules/*
|
||||
!Oqtane.Server/wwwroot/Modules/Oqtane.Modules.*
|
||||
!Oqtane.Server/wwwroot/Modules/Templates
|
||||
Oqtane.Server/wwwroot/Modules/Templates/*
|
||||
!Oqtane.Server/wwwroot/Modules/Templates/External
|
||||
|
||||
Oqtane.Server/wwwroot/Themes
|
||||
Oqtane.Server/wwwroot/Themes/*
|
||||
!Oqtane.Server/wwwroot/Themes/Oqtane.Themes.*
|
||||
!Oqtane.Server/wwwroot/Themes/Templates
|
||||
Oqtane.Server/wwwroot/Themes/Templates/*
|
||||
Oqtane.Server/wwwroot/Themes/Templates/External
|
||||
|
@ -51,6 +51,7 @@ namespace Microsoft.Extensions.DependencyInjection
|
||||
services.AddScoped<IUrlMappingService, UrlMappingService>();
|
||||
services.AddScoped<IVisitorService, VisitorService>();
|
||||
services.AddScoped<ISyncService, SyncService>();
|
||||
services.AddScoped<ILocalizationCookieService, LocalizationCookieService>();
|
||||
|
||||
// providers
|
||||
services.AddScoped<ITextEditor, Oqtane.Modules.Controls.QuillJSTextEditor>();
|
||||
|
@ -15,7 +15,7 @@
|
||||
<div class="row">
|
||||
<div class="mx-auto text-center">
|
||||
<img src="oqtane-black.png" />
|
||||
<div style="font-weight: bold">@SharedLocalizer["Version"] @Constants.Version (.NET 8)</div>
|
||||
<div style="font-weight: bold">@SharedLocalizer["Version"] @Constants.Version (.NET 9)</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="app-rule" />
|
||||
@ -156,129 +156,130 @@
|
||||
private List<SiteTemplate> _templates;
|
||||
private string _template = Constants.DefaultSiteTemplate;
|
||||
private bool _register = true;
|
||||
private string _message = string.Empty;
|
||||
private string _loadingDisplay = "display: none;";
|
||||
private string _message = string.Empty;
|
||||
private string _loadingDisplay = "display: none;";
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
// include CSS
|
||||
var content = "<link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/css/bootstrap.min.css\" integrity=\"sha512-t4GWSVZO1eC8BM339Xd7Uphw5s17a86tIZIj8qRxhnKub6WoyhnrxeCIMeAqBPgdZGlCcG2PrZjMc+Wr78+5Xg==\" crossorigin=\"anonymous\" type=\"text/css\"/>";
|
||||
var content = $"<link rel=\"stylesheet\" href=\"{Constants.BootstrapStylesheetUrl}\" integrity=\"{Constants.BootstrapStylesheetIntegrity}\" crossorigin=\"anonymous\" type=\"text/css\"/>";
|
||||
SiteState.AppendHeadContent(content);
|
||||
|
||||
_togglePassword = SharedLocalizer["ShowPassword"];
|
||||
_toggleConfirmPassword = SharedLocalizer["ShowPassword"];
|
||||
_toggleConfirmPassword = SharedLocalizer["ShowPassword"];
|
||||
|
||||
_databases = await DatabaseService.GetDatabasesAsync();
|
||||
if (_databases.Exists(item => item.IsDefault))
|
||||
{
|
||||
_databaseName = _databases.Find(item => item.IsDefault).Name;
|
||||
}
|
||||
else
|
||||
{
|
||||
_databaseName = "LocalDB";
|
||||
}
|
||||
LoadDatabaseConfigComponent();
|
||||
_databases = await DatabaseService.GetDatabasesAsync();
|
||||
if (_databases.Exists(item => item.IsDefault))
|
||||
{
|
||||
_databaseName = _databases.Find(item => item.IsDefault).Name;
|
||||
}
|
||||
else
|
||||
{
|
||||
_databaseName = "LocalDB";
|
||||
}
|
||||
LoadDatabaseConfigComponent();
|
||||
|
||||
_templates = await SiteTemplateService.GetSiteTemplatesAsync();
|
||||
}
|
||||
|
||||
private void DatabaseChanged(ChangeEventArgs eventArgs)
|
||||
{
|
||||
try
|
||||
{
|
||||
_databaseName = (string)eventArgs.Value;
|
||||
_showConnectionString = false;
|
||||
LoadDatabaseConfigComponent();
|
||||
}
|
||||
catch
|
||||
{
|
||||
_message = Localizer["Error.DbConfig.Load"];
|
||||
}
|
||||
}
|
||||
private void DatabaseChanged(ChangeEventArgs eventArgs)
|
||||
{
|
||||
try
|
||||
{
|
||||
_databaseName = (string)eventArgs.Value;
|
||||
_showConnectionString = false;
|
||||
LoadDatabaseConfigComponent();
|
||||
}
|
||||
catch
|
||||
{
|
||||
_message = Localizer["Error.DbConfig.Load"];
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadDatabaseConfigComponent()
|
||||
{
|
||||
var database = _databases.SingleOrDefault(d => d.Name == _databaseName);
|
||||
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 == _databaseName);
|
||||
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();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
// include JavaScript
|
||||
var interop = new Interop(JSRuntime);
|
||||
await interop.IncludeScript("", "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/js/bootstrap.bundle.min.js", "sha512-VK2zcvntEufaimc+efOYi622VN5ZacdnufnmX7zIhCPmjhKnOi9ZDMtg1/ug5l183f19gG1/cBstPO4D8N/Img==", "anonymous", "", "head");
|
||||
}
|
||||
}
|
||||
var interop = new Interop(JSRuntime);
|
||||
await interop.IncludeScript("", Constants.BootstrapScriptUrl, Constants.BootstrapScriptIntegrity, "anonymous", "", "head");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Install()
|
||||
{
|
||||
var connectionString = String.Empty;
|
||||
if (_showConnectionString)
|
||||
{
|
||||
connectionString = _connectionString;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_databaseConfig is IDatabaseConfigControl databaseConfigControl)
|
||||
{
|
||||
connectionString = databaseConfigControl.GetConnectionString();
|
||||
}
|
||||
}
|
||||
private async Task Install()
|
||||
{
|
||||
var connectionString = String.Empty;
|
||||
if (_showConnectionString)
|
||||
{
|
||||
connectionString = _connectionString;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_databaseConfig is IDatabaseConfigControl databaseConfigControl)
|
||||
{
|
||||
connectionString = databaseConfigControl.GetConnectionString();
|
||||
}
|
||||
}
|
||||
|
||||
if (connectionString != "" && !string.IsNullOrEmpty(_hostUsername) && !string.IsNullOrEmpty(_hostPassword) && _hostPassword == _confirmPassword && !string.IsNullOrEmpty(_hostEmail) && _hostEmail.Contains("@"))
|
||||
{
|
||||
if (await UserService.ValidatePasswordAsync(_hostPassword))
|
||||
{
|
||||
_loadingDisplay = "";
|
||||
StateHasChanged();
|
||||
if (connectionString != "" && !string.IsNullOrEmpty(_hostUsername) && !string.IsNullOrEmpty(_hostPassword) && _hostPassword == _confirmPassword && !string.IsNullOrEmpty(_hostEmail) && _hostEmail.Contains("@"))
|
||||
{
|
||||
var result = await UserService.ValidateUserAsync(_hostUsername, _hostEmail, _hostPassword);
|
||||
if (result.Succeeded)
|
||||
{
|
||||
_loadingDisplay = "";
|
||||
StateHasChanged();
|
||||
|
||||
Uri uri = new Uri(NavigationManager.Uri);
|
||||
Uri uri = new Uri(NavigationManager.Uri);
|
||||
|
||||
var database = _databases.SingleOrDefault(d => d.Name == _databaseName);
|
||||
var database = _databases.SingleOrDefault(d => d.Name == _databaseName);
|
||||
|
||||
var config = new InstallConfig
|
||||
{
|
||||
DatabaseType = database.DBType,
|
||||
ConnectionString = connectionString,
|
||||
Aliases = uri.Authority,
|
||||
HostUsername = _hostUsername,
|
||||
HostPassword = _hostPassword,
|
||||
HostEmail = _hostEmail,
|
||||
HostName = _hostUsername,
|
||||
TenantName = TenantNames.Master,
|
||||
IsNewTenant = true,
|
||||
SiteName = Constants.DefaultSite,
|
||||
Register = _register,
|
||||
SiteTemplate = _template,
|
||||
RenderMode = RenderModes.Static,
|
||||
Runtime = Runtimes.Server
|
||||
};
|
||||
var config = new InstallConfig
|
||||
{
|
||||
DatabaseType = database.DBType,
|
||||
ConnectionString = connectionString,
|
||||
Aliases = uri.Authority,
|
||||
HostUsername = _hostUsername,
|
||||
HostPassword = _hostPassword,
|
||||
HostEmail = _hostEmail,
|
||||
HostName = _hostUsername,
|
||||
TenantName = TenantNames.Master,
|
||||
IsNewTenant = true,
|
||||
SiteName = Constants.DefaultSite,
|
||||
Register = _register,
|
||||
SiteTemplate = _template,
|
||||
RenderMode = RenderModes.Static,
|
||||
Runtime = Runtimes.Server
|
||||
};
|
||||
|
||||
var installation = await InstallationService.Install(config);
|
||||
if (installation.Success)
|
||||
{
|
||||
NavigationManager.NavigateTo(uri.Scheme + "://" + uri.Authority, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
_message = installation.Message;
|
||||
_loadingDisplay = "display: none;";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_message = Localizer["Message.Password.Invalid"];
|
||||
var installation = await InstallationService.Install(config);
|
||||
if (installation.Success)
|
||||
{
|
||||
NavigationManager.NavigateTo(uri.Scheme + "://" + uri.Authority, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
_message = installation.Message;
|
||||
_loadingDisplay = "display: none;";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_message = string.Join("<br />", result.Errors.Select(i => !string.IsNullOrEmpty(i.Value) ? i.Value : Localizer[i.Key]));
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -40,10 +40,14 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="SaveFile">@SharedLocalizer["Save"]</button>
|
||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||
<br />
|
||||
<br />
|
||||
@if (_name.ToLower().EndsWith(".zip"))
|
||||
{
|
||||
<button type="button" class="btn btn-primary mx-1" @onclick="UnzipFile">Unzip</button>
|
||||
}
|
||||
<br /><br />
|
||||
<AuditInfo CreatedBy="@_createdBy" CreatedOn="@_createdOn" ModifiedBy="@_modifiedBy" ModifiedOn="@_modifiedOn"></AuditInfo>
|
||||
</form>
|
||||
}
|
||||
@ -126,4 +130,18 @@
|
||||
AddModuleMessage(SharedLocalizer["Message.InfoRequired"], MessageType.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UnzipFile()
|
||||
{
|
||||
try
|
||||
{
|
||||
await FileService.UnzipFileAsync(_fileId);
|
||||
NavigationManager.NavigateTo(NavigateUrl());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Unzipping File {FileId} {Error}", _fileId, ex.Message);
|
||||
AddModuleMessage(Localizer["Error.File.Unzip"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
}
|
@ -8,67 +8,77 @@
|
||||
|
||||
@if (_folders != null)
|
||||
{
|
||||
<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="parent" HelpText="Select the parent folder" ResourceKey="Parent">Parent: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="parent" class="form-select" @bind="@_parentId" required>
|
||||
@if (PageState.QueryString.ContainsKey("id"))
|
||||
{
|
||||
<option value="-1"><@Localizer["NoParent"]></option>
|
||||
}
|
||||
@foreach (Folder folder in _folders)
|
||||
{
|
||||
<option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option>
|
||||
}
|
||||
</select>
|
||||
<TabStrip>
|
||||
<TabPanel Name="Settings" ResourceKey="Settings" Heading="Settings">
|
||||
<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="parent" HelpText="Select the parent folder" ResourceKey="Parent">Parent: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="parent" class="form-select" @bind="@_parentId" required>
|
||||
@if (PageState.QueryString.ContainsKey("id"))
|
||||
{
|
||||
<option value="-1"><@Localizer["NoParent"]></option>
|
||||
}
|
||||
@foreach (Folder folder in _folders)
|
||||
{
|
||||
<option value="@(folder.FolderId)">@(new string('-', folder.Level * 2))@(folder.Name)</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="name" HelpText="Enter the folder name" ResourceKey="Name">Name: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="name" class="form-control" @bind="@_name" maxlength="256" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="type" HelpText="Select the folder type. Private folders are only accessible by authorized users. Public folders can be accessed by all users" ResourceKey="Type">Type: </Label>
|
||||
<div class="col-sm-9">
|
||||
@if (PageState.QueryString.ContainsKey("id"))
|
||||
{
|
||||
<input id="type" class="form-control" readonly @bind="@_type" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<select id="type" class="form-select" @bind="@_type" required>
|
||||
<option value="@FolderTypes.Private">@Localizer[FolderTypes.Private]</option>
|
||||
<option value="@FolderTypes.Public">@Localizer[FolderTypes.Public]</option>
|
||||
</select>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="imagesizes" HelpText="Enter a list of image sizes which can be generated dynamically from uploaded images (ie. 200x200,400x400). Use * to indicate the folder supports all image sizes." ResourceKey="ImageSizes">Image Sizes: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="imagesizes" class="form-control" @bind="@_imagesizes" maxlength="512" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="capacity" HelpText="Enter the maximum folder capacity (in megabytes). Specify zero if the capacity is unlimited." ResourceKey="Capacity">Capacity: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="capacity" class="form-control" @bind="@_capacity" required />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="name" HelpText="Enter the folder name" ResourceKey="Name">Name: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="name" class="form-control" @bind="@_name" maxlength="256" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="type" HelpText="Select the folder type. Private folders are only accessible by authorized users. Public folders can be accessed by all users" ResourceKey="Type">Type: </Label>
|
||||
<div class="col-sm-9">
|
||||
@if (PageState.QueryString.ContainsKey("id"))
|
||||
{
|
||||
<input id="type" class="form-control" readonly @bind="@_type" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<select id="type" class="form-select" @bind="@_type" required>
|
||||
<option value="@FolderTypes.Private">@Localizer[FolderTypes.Private]</option>
|
||||
<option value="@FolderTypes.Public">@Localizer[FolderTypes.Public]</option>
|
||||
</select>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="imagesizes" HelpText="Enter a list of image sizes which can be generated dynamically from uploaded images (ie. 200x200,400x400). Use * to indicate the folder supports all image sizes." ResourceKey="ImageSizes">Image Sizes: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="imagesizes" class="form-control" @bind="@_imagesizes" maxlength="512" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="capacity" HelpText="Enter the maximum folder capacity (in megabytes). Specify zero if the capacity is unlimited." ResourceKey="Capacity">Capacity: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="capacity" class="form-control" @bind="@_capacity" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<div class="col-sm-12">
|
||||
<Label Class="col-sm-3" For="permissions" HelpText="Select the permissions you want for the folder" ResourceKey="Permissions">Permissions: </Label>
|
||||
@if (PageState.QueryString.ContainsKey("id"))
|
||||
{
|
||||
<br />
|
||||
<AuditInfo CreatedBy="@_createdBy" CreatedOn="@_createdOn" ModifiedBy="@_modifiedBy" ModifiedOn="@_modifiedOn"></AuditInfo>
|
||||
}
|
||||
</form>
|
||||
</TabPanel>
|
||||
<TabPanel Name="Permissions" ResourceKey="Permissions" Heading="Permissions">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<PermissionGrid EntityName="@EntityNames.Folder" PermissionNames="@(PermissionNames.Browse + "," + PermissionNames.View + "," + PermissionNames.Edit)" PermissionList="@_permissions" @ref="_permissionGrid" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<br /><br />
|
||||
</TabPanel>
|
||||
</TabStrip>
|
||||
|
||||
<br />
|
||||
@if (!_isSystem)
|
||||
{
|
||||
<button type="button" class="btn btn-success" @onclick="SaveFolder">@SharedLocalizer["Save"]</button>
|
||||
@ -80,11 +90,6 @@
|
||||
@((MarkupString)" ")
|
||||
<ActionDialog Header="Delete Folder" Message="Are You Sure You Wish To Delete This Folder?" Action="Delete" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteFolder())" ResourceKey="DeleteFolder" />
|
||||
}
|
||||
<br /><br />
|
||||
@if (PageState.QueryString.ContainsKey("id"))
|
||||
{
|
||||
<AuditInfo CreatedBy="@_createdBy" CreatedOn="@_createdOn" ModifiedBy="@_modifiedBy" ModifiedOn="@_modifiedOn"></AuditInfo>
|
||||
}
|
||||
}
|
||||
|
||||
@code {
|
||||
|
@ -152,7 +152,8 @@ else
|
||||
|
||||
private async Task Refresh()
|
||||
{
|
||||
ShowProgressIndicator();
|
||||
await GetJobs();
|
||||
StateHasChanged();
|
||||
HideProgressIndicator();
|
||||
}
|
||||
}
|
||||
|
@ -78,7 +78,8 @@ else
|
||||
|
||||
private async Task Refresh()
|
||||
{
|
||||
ShowProgressIndicator();
|
||||
await GetJobLogs();
|
||||
StateHasChanged();
|
||||
HideProgressIndicator();
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
@namespace Oqtane.Modules.Admin.Languages
|
||||
@inherits ModuleBase
|
||||
@using System.Globalization
|
||||
@using Microsoft.AspNetCore.Localization
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject ILocalizationService LocalizationService
|
||||
@inject ILanguageService LanguageService
|
||||
@ -94,7 +93,6 @@ else
|
||||
var language = new Language
|
||||
{
|
||||
SiteId = PageState.Page.SiteId,
|
||||
Name = CultureInfo.GetCultureInfo(_code).DisplayName,
|
||||
Code = _code,
|
||||
IsDefault = (_default == null ? false : Boolean.Parse(_default))
|
||||
};
|
||||
@ -130,7 +128,7 @@ else
|
||||
{
|
||||
var interop = new Interop(JSRuntime);
|
||||
var localizationCookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture));
|
||||
await interop.SetCookie(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, 360);
|
||||
await interop.SetCookie(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, 360, true, "Lax");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
@namespace Oqtane.Modules.Admin.Languages
|
||||
@inherits ModuleBase
|
||||
@using System.Globalization
|
||||
@using Microsoft.AspNetCore.Localization
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject ILocalizationService LocalizationService
|
||||
@inject ILanguageService LanguageService
|
||||
@ -103,7 +102,7 @@ else
|
||||
{
|
||||
var interop = new Interop(JSRuntime);
|
||||
var localizationCookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture));
|
||||
await interop.SetCookie(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, 360);
|
||||
await interop.SetCookie(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, 360, true, "Lax");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,74 +8,77 @@
|
||||
@inject IStringLocalizer<Index> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
|
||||
<AuthorizeView Roles="@RoleNames.Registered">
|
||||
<Authorizing>
|
||||
<text>...</text>
|
||||
</Authorizing>
|
||||
<Authorized>
|
||||
<ModuleMessage Message="@Localizer["Info.SignedIn"]" Type="MessageType.Info" />
|
||||
</Authorized>
|
||||
<NotAuthorized>
|
||||
@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" @onclick="ExternalLogin">@Localizer["Use"] @PageState.Site.Settings["ExternalLogin:ProviderName"]</button>
|
||||
<br /><br />
|
||||
}
|
||||
@if (_allowsitelogin)
|
||||
{
|
||||
<div class="form-group">
|
||||
<Label Class="control-label" For="username" HelpText="Please enter your Username" ResourceKey="Username">Username:</Label>
|
||||
<input id="username" type="text" @ref="username" class="form-control" placeholder="@Localizer["Username.Placeholder"]" @bind="@_username" required />
|
||||
</div>
|
||||
<div class="form-group 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" required />
|
||||
@if (PageState.User != null)
|
||||
{
|
||||
<ModuleMessage Message="@Localizer["Info.SignedIn"]" Type="MessageType.Info" />
|
||||
}
|
||||
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" @onclick="ExternalLogin">@Localizer["Use"] @PageState.Site.Settings["ExternalLogin:ProviderName"]</button>
|
||||
<br />
|
||||
|
||||
<br />
|
||||
}
|
||||
@if (_allowsitelogin)
|
||||
{
|
||||
<div class="form-group">
|
||||
<Label Class="control-label" For="username" HelpText="Please enter your Username" ResourceKey="Username">Username:</Label>
|
||||
<input id="username" type="text" @ref="username" class="form-control" placeholder="@Localizer["Username.Placeholder"]" @bind="@_username" required />
|
||||
</div>
|
||||
<div class="form-group 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" required />
|
||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group mt-2">
|
||||
@if (!_alwaysremember)
|
||||
{
|
||||
<div class="form-check">
|
||||
<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>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<button type="button" class="btn btn-primary" @onclick="Login">@SharedLocalizer["Login"]</button>
|
||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||
<br /><br />
|
||||
<button type="button" class="btn btn-secondary" @onclick="Forgot">@Localizer["ForgotPassword"]</button>
|
||||
@if (PageState.Site.AllowRegistration)
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group mt-2">
|
||||
@if (!_alwaysremember)
|
||||
{
|
||||
<br /><br />
|
||||
<NavLink href="@NavigateUrl("register")">@Localizer["Register"]</NavLink>
|
||||
<div class="form-check">
|
||||
<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>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<button type="button" class="btn btn-primary" @onclick="Login">@SharedLocalizer["Login"]</button>
|
||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||
<br />
|
||||
|
||||
<br />
|
||||
<button type="button" class="btn btn-secondary" @onclick="Forgot">@Localizer["ForgotPassword"]</button>
|
||||
@if (PageState.Site.AllowRegistration)
|
||||
{
|
||||
<br />
|
||||
|
||||
<br />
|
||||
<NavLink href="@NavigateUrl("register")">@Localizer["Register"]</NavLink>
|
||||
}
|
||||
</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>
|
||||
}
|
||||
</NotAuthorized>
|
||||
</AuthorizeView>
|
||||
}
|
||||
</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>
|
||||
}
|
||||
}
|
||||
|
||||
@code {
|
||||
private bool _allowsitelogin = true;
|
||||
@ -204,28 +207,31 @@
|
||||
user = await UserService.VerifyTwoFactorAsync(user, _code);
|
||||
}
|
||||
|
||||
if (user.IsAuthenticated)
|
||||
if (user != null && user.IsAuthenticated)
|
||||
{
|
||||
await logger.LogInformation(LogFunction.Security, "Login Successful For Username {Username}", _username);
|
||||
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(PageState.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(PageState.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);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:TwoFactor", "false") == "required" || user.TwoFactorRequired)
|
||||
if (SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:TwoFactor", "false") == "required" || (user != null && user.TwoFactorRequired))
|
||||
{
|
||||
twofactor = true;
|
||||
validated = false;
|
||||
@ -236,12 +242,12 @@
|
||||
if (!twofactor)
|
||||
{
|
||||
await logger.LogInformation(LogFunction.Security, "Login Failed For Username {Username}", _username);
|
||||
AddModuleMessage(Localizer["Error.Login.Fail"], MessageType.Error);
|
||||
AddModuleMessage(Localizer["Error.Login.Fail"], MessageType.Error);
|
||||
}
|
||||
else
|
||||
{
|
||||
await logger.LogInformation(LogFunction.Security, "Two Factor Verification Failed For Username {Username}", _username);
|
||||
AddModuleMessage(Localizer["Error.TwoFactor.Fail"], MessageType.Error);
|
||||
AddModuleMessage(Localizer["Error.TwoFactor.Fail"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,7 @@
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="description" HelpText="Enter a short description for the module" ResourceKey="Description">Description: </Label>
|
||||
<div class="col-sm-9">
|
||||
<textarea id="description" class="form-control" @bind="@_description" rows="3" maxlength="2000" required></textarea>
|
||||
<textarea id="description" class="form-control" @bind="@_description" rows="3" maxlength="2000"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
@ -118,6 +118,7 @@
|
||||
{
|
||||
if (IsValid(_owner) && IsValid(_module) && _owner != _module && _template != "-")
|
||||
{
|
||||
if (string.IsNullOrEmpty(_description)) _description = _module;
|
||||
if (IsValidXML(_description))
|
||||
{
|
||||
var template = _templates.FirstOrDefault(item => item.Name == _template);
|
||||
|
@ -1,7 +1,6 @@
|
||||
@namespace Oqtane.Modules.Admin.ModuleDefinitions
|
||||
@inherits ModuleBase
|
||||
@using System.Globalization
|
||||
@using Microsoft.AspNetCore.Localization
|
||||
@inject IModuleDefinitionService ModuleDefinitionService
|
||||
@inject IPackageService PackageService
|
||||
@inject ILanguageService LanguageService
|
||||
|
@ -15,7 +15,7 @@
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn btn-success" @onclick="ExportModule">@Localizer["Export"]</button>
|
||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||
<NavLink class="btn btn-secondary" href="@PageState.ReturnUrl">@SharedLocalizer["Cancel"]</NavLink>
|
||||
|
||||
@code {
|
||||
private string _content = string.Empty;
|
||||
|
@ -17,7 +17,7 @@
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn btn-success" @onclick="ImportModule">@Localizer["Import"]</button>
|
||||
<NavLink class="btn btn-secondary" href="@NavigateUrl()">@SharedLocalizer["Cancel"]</NavLink>
|
||||
<NavLink class="btn btn-secondary" href="@PageState.ReturnUrl">@SharedLocalizer["Cancel"]</NavLink>
|
||||
</form>
|
||||
|
||||
@code {
|
||||
|
@ -9,129 +9,133 @@
|
||||
@inject IStringLocalizer<Settings> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
|
||||
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||
<TabStrip>
|
||||
<TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings">
|
||||
@if (_containers != null)
|
||||
{
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="module" HelpText="The name of the module" ResourceKey="Module">Module: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="module" type="text" class="form-control" @bind="@_module" disabled />
|
||||
@if (_initialized)
|
||||
{
|
||||
<form @ref="form" class="@(validated ? "was-validated" : "needs-validation")" novalidate>
|
||||
<TabStrip>
|
||||
<TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings">
|
||||
@if (_containers != null)
|
||||
{
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="module" HelpText="The name of the module" ResourceKey="Module">Module: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="module" type="text" class="form-control" @bind="@_module" disabled />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="title" HelpText="Enter the title of the module" ResourceKey="Title">Title: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="title" type="text" class="form-control" @bind="@_title" required />
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="title" HelpText="Enter the title of the module" ResourceKey="Title">Title: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="title" type="text" class="form-control" @bind="@_title" required />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="pane" HelpText="The pane where the module will be displayed" ResourceKey="Pane">Pane: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select class="form-select" @bind="@_pane">
|
||||
@foreach (string pane in PageState.Page.Panes)
|
||||
{
|
||||
<option value="@pane">@pane Pane</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="container" HelpText="Select the module's container" ResourceKey="Container">Container: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="container" class="form-select" @bind="@_containerType" required>
|
||||
@foreach (var container in _containers)
|
||||
{
|
||||
<option value="@container.TypeName">@container.Name</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="effectiveDate" HelpText="The date that this module 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 module 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="allpages" HelpText="Indicate if this module should be displayed on all pages" ResourceKey="DisplayOnAllPages">Display On All Pages? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="allpages" class="form-select" @bind="@_allPages" 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="page" HelpText="The page that the module is located on" ResourceKey="Page">Page: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="page" class="form-select" @bind="@_pageId" required>
|
||||
@if (PageState.Page.UserId != null)
|
||||
{
|
||||
<option value="@PageState.Page.PageId">@(PageState.Page.Name)</option>
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_pages != null)
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="pane" HelpText="The pane where the module will be displayed" ResourceKey="Pane">Pane: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select class="form-select" @bind="@_pane">
|
||||
@foreach (string pane in PageState.Page.Panes)
|
||||
{
|
||||
foreach (Page p in _pages)
|
||||
<option value="@pane">@pane Pane</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="container" HelpText="Select the module's container" ResourceKey="Container">Container: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="container" class="form-select" @bind="@_containerType" required>
|
||||
@foreach (var container in _containers)
|
||||
{
|
||||
<option value="@container.TypeName">@container.Name</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="effectiveDate" HelpText="The date that this module 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 module 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="allpages" HelpText="Indicate if this module should be displayed on all pages" ResourceKey="DisplayOnAllPages">Display On All Pages? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="allpages" class="form-select" @bind="@_allPages" 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="page" HelpText="The page that the module is located on" ResourceKey="Page">Page: </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="page" class="form-select" @bind="@_pageId" required>
|
||||
@if (PageState.Page.UserId != null)
|
||||
{
|
||||
<option value="@PageState.Page.PageId">@(PageState.Page.Name)</option>
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_pages != null)
|
||||
{
|
||||
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, p.PermissionList))
|
||||
foreach (Page p in _pages)
|
||||
{
|
||||
<option value="@p.PageId">@(new string('-', p.Level * 2))@(p.Name)</option>
|
||||
if (UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, p.PermissionList))
|
||||
{
|
||||
<option value="@p.PageId">@(new string('-', p.Level * 2))@(p.Name)</option>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</select>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</TabPanel>
|
||||
<TabPanel Name="Permissions" Heading="Permissions" ResourceKey="Permissions">
|
||||
@if (_permissions != null)
|
||||
{
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<PermissionGrid EntityName="@EntityNames.Module" PermissionNames="@_permissionNames" PermissionList="@_permissions" @ref="_permissionGrid" />
|
||||
}
|
||||
</TabPanel>
|
||||
<TabPanel Name="Permissions" Heading="Permissions" ResourceKey="Permissions">
|
||||
@if (_permissions != null)
|
||||
{
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<PermissionGrid EntityName="@EntityNames.Module" PermissionNames="@_permissionNames" PermissionList="@_permissions" @ref="_permissionGrid" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
}
|
||||
</TabPanel>
|
||||
@if (_moduleSettingsType != null)
|
||||
{
|
||||
<TabPanel Name="ModuleSettings" Heading="@_moduleSettingsTitle" ResourceKey="ModuleSettings">
|
||||
@ModuleSettingsComponent
|
||||
</TabPanel>
|
||||
}
|
||||
</TabPanel>
|
||||
@if (_moduleSettingsType != null)
|
||||
{
|
||||
<TabPanel Name="ModuleSettings" Heading="@_moduleSettingsTitle" ResourceKey="ModuleSettings">
|
||||
@ModuleSettingsComponent
|
||||
</TabPanel>
|
||||
}
|
||||
@if (_containerSettingsType != null)
|
||||
{
|
||||
<TabPanel Name="ContainerSettings" Heading="Container Settings" ResourceKey="ContainerSettings">
|
||||
@ContainerSettingsComponent
|
||||
</TabPanel>
|
||||
}
|
||||
</TabStrip>
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="SaveModule">@SharedLocalizer["Save"]</button>
|
||||
<NavLink class="btn btn-secondary" href="@PageState.ReturnUrl">@SharedLocalizer["Cancel"]</NavLink>
|
||||
<br />
|
||||
<br />
|
||||
<AuditInfo CreatedBy="@createdby" CreatedOn="@createdon" ModifiedBy="@modifiedby" ModifiedOn="@modifiedon"></AuditInfo>
|
||||
</form>
|
||||
@if (_containerSettingsType != null)
|
||||
{
|
||||
<TabPanel Name="ContainerSettings" Heading="Container Settings" ResourceKey="ContainerSettings">
|
||||
@ContainerSettingsComponent
|
||||
</TabPanel>
|
||||
}
|
||||
</TabStrip>
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="SaveModule">@SharedLocalizer["Save"]</button>
|
||||
<NavLink class="btn btn-secondary" href="@PageState.ReturnUrl">@SharedLocalizer["Cancel"]</NavLink>
|
||||
<br />
|
||||
<br />
|
||||
<AuditInfo CreatedBy="@createdby" CreatedOn="@createdon" ModifiedBy="@modifiedby" ModifiedOn="@modifiedon"></AuditInfo>
|
||||
</form>
|
||||
}
|
||||
|
||||
@code {
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Edit;
|
||||
|
||||
private bool _initialized = false;
|
||||
private ElementReference form;
|
||||
private bool validated = false;
|
||||
private List<ThemeControl> _containers = new List<ThemeControl>();
|
||||
@ -163,7 +167,6 @@
|
||||
{
|
||||
SetModuleTitle(Localizer["ModuleSettings.Title"]);
|
||||
|
||||
_module = ModuleState.ModuleDefinition.Name;
|
||||
_title = ModuleState.Title;
|
||||
_moduleSettingsTitle = Localizer["ModuleSettings.Heading"];
|
||||
_pane = ModuleState.Pane;
|
||||
@ -182,6 +185,7 @@
|
||||
|
||||
if (ModuleState.ModuleDefinition != null)
|
||||
{
|
||||
_module = ModuleState.ModuleDefinition.Name;
|
||||
_permissionNames = ModuleState.ModuleDefinition?.PermissionNames;
|
||||
|
||||
if (!string.IsNullOrEmpty(ModuleState.ModuleDefinition.SettingsType))
|
||||
@ -231,6 +235,8 @@
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
private async Task SaveModule()
|
||||
|
@ -156,7 +156,14 @@
|
||||
<select id="theme" class="form-select" value="@_themetype" @onchange="(e => ThemeChanged(e))" required>
|
||||
@foreach (var theme in _themes)
|
||||
{
|
||||
<option value="@theme.TypeName">@theme.Name</option>
|
||||
@if (theme.TypeName == PageState.Site.DefaultThemeType)
|
||||
{
|
||||
<option value="@theme.TypeName">*@theme.Name*</option>
|
||||
}
|
||||
else
|
||||
{
|
||||
<option value="@theme.TypeName">@theme.Name</option>
|
||||
}
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
|
@ -5,7 +5,8 @@
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IPageService PageService
|
||||
@inject IPageModuleService PageModuleService
|
||||
@inject IThemeService ThemeService
|
||||
@inject IModuleService ModuleService
|
||||
@inject IThemeService ThemeService
|
||||
@inject ISystemService SystemService
|
||||
@inject IStringLocalizer<Edit> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
@ -171,7 +172,14 @@
|
||||
<select id="theme" class="form-select" value="@_themetype" @onchange="(e => ThemeChanged(e))" required>
|
||||
@foreach (var theme in _themes)
|
||||
{
|
||||
<option value="@theme.TypeName">@theme.Name</option>
|
||||
@if (theme.TypeName == PageState.Site.DefaultThemeType)
|
||||
{
|
||||
<option value="@theme.TypeName">*@theme.Name*</option>
|
||||
}
|
||||
else
|
||||
{
|
||||
<option value="@theme.TypeName">@theme.Name</option>
|
||||
}
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
@ -261,7 +269,14 @@
|
||||
<select id="theme" class="form-select" @bind="@_themetype" required>
|
||||
@foreach (var theme in _themes)
|
||||
{
|
||||
<option value="@theme.TypeName">@theme.Name</option>
|
||||
@if (theme.TypeName == PageState.Site.DefaultThemeType)
|
||||
{
|
||||
<option value="@theme.TypeName">*@theme.Name*</option>
|
||||
}
|
||||
else
|
||||
{
|
||||
<option value="@theme.TypeName">@theme.Name</option>
|
||||
}
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
@ -417,7 +432,8 @@
|
||||
_permissions = _page.PermissionList;
|
||||
|
||||
// page modules
|
||||
_pageModules = PageState.Modules;
|
||||
var modules = await ModuleService.GetModulesAsync(PageState.Site.SiteId);
|
||||
_pageModules = modules.Where(item => item.PageId == _page.PageId && !item.IsDeleted).ToList();
|
||||
|
||||
// audit
|
||||
_createdby = _page.CreatedBy;
|
||||
|
@ -22,7 +22,7 @@ else
|
||||
}
|
||||
else
|
||||
{
|
||||
<Pager Items="@_pages.Where(item => item.IsDeleted)" CurrentPage="@_pagePage.ToString()" OnPageChange="OnPageChangePage">
|
||||
<Pager Items="@_pages.Where(item => item.IsDeleted).OrderByDescending(item => item.DeletedOn)" CurrentPage="@_pagePage.ToString()" OnPageChange="OnPageChangePage">
|
||||
<Header>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
@ -50,7 +50,7 @@ else
|
||||
}
|
||||
else
|
||||
{
|
||||
<Pager Items="@_modules.Where(item => item.IsDeleted)" CurrentPage="@_pageModule.ToString()" OnPageChange="OnPageChangeModule">
|
||||
<Pager Items="@_modules.Where(item => item.IsDeleted).OrderByDescending(item => item.DeletedOn)" CurrentPage="@_pageModule.ToString()" OnPageChange="OnPageChangeModule">
|
||||
<Header>
|
||||
<th style="width: 1px;"> </th>
|
||||
<th style="width: 1px;"> </th>
|
||||
|
@ -11,65 +11,64 @@
|
||||
{
|
||||
if (!_userCreated)
|
||||
{
|
||||
<AuthorizeView Roles="@RoleNames.Registered">
|
||||
<Authorizing>
|
||||
<text>...</text>
|
||||
</Authorizing>
|
||||
<Authorized>
|
||||
<ModuleMessage Message="@Localizer["Info.Registration.Exists"]" Type="MessageType.Info" />
|
||||
</Authorized>
|
||||
<NotAuthorized>
|
||||
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
|
||||
<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="username" HelpText="Your username. Note that this field can not be modified once it is saved." ResourceKey="Username"></Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="username" class="form-control" @bind="@_username" maxlength="256" required />
|
||||
</div>
|
||||
if (PageState.User != null)
|
||||
{
|
||||
<ModuleMessage Message="@Localizer["Info.Registration.Exists"]" Type="MessageType.Info" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
|
||||
<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="username" HelpText="Your username. Note that this field can not be modified once it is saved." ResourceKey="Username"></Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="username" class="form-control" @bind="@_username" maxlength="256" required />
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="password" HelpText="Please choose a sufficiently secure password and enter it here" ResourceKey="Password"></Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" required />
|
||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="confirm" HelpText="Enter your password again to confirm it matches the value entered above" ResourceKey="Confirm"></Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@_confirm" autocomplete="new-password" required />
|
||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="email" HelpText="Your email address where you wish to receive notifications" ResourceKey="Email"></Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="email" class="form-control" @bind="@_email" maxlength="256" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="displayname" HelpText="Your full name" ResourceKey="DisplayName"></Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="displayname" class="form-control" @bind="@_displayname" maxlength="50" />
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="password" HelpText="Please choose a sufficiently secure password and enter it here" ResourceKey="Password"></Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" required />
|
||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="confirm" HelpText="Enter your password again to confirm it matches the value entered above" ResourceKey="Confirm"></Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@_confirm" autocomplete="new-password" required />
|
||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="email" HelpText="Your email address where you wish to receive notifications" ResourceKey="Email"></Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="email" class="form-control" @bind="@_email" maxlength="256" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="displayname" HelpText="Your full name" ResourceKey="DisplayName"></Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="displayname" class="form-control" @bind="@_displayname" maxlength="50" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<button type="button" class="btn btn-primary" @onclick="Register">@Localizer["Register"]</button>
|
||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||
@if (_allowsitelogin)
|
||||
{
|
||||
<br />
|
||||
<button type="button" class="btn btn-primary" @onclick="Register">@Localizer["Register"]</button>
|
||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||
@if (_allowsitelogin)
|
||||
{
|
||||
<br /><br />
|
||||
<NavLink href="@NavigateUrl("login")">@Localizer["Login"]</NavLink>
|
||||
}
|
||||
</form>
|
||||
</NotAuthorized>
|
||||
</AuthorizeView>
|
||||
|
||||
<br />
|
||||
<NavLink href="@NavigateUrl("login")">@Localizer["Login"]</NavLink>
|
||||
}
|
||||
</form>
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -53,38 +53,39 @@
|
||||
</div>
|
||||
<br /><br />
|
||||
<button type="button" class="btn btn-success" @onclick="Save">@SharedLocalizer["Save"]</button>
|
||||
<ActionDialog Header="Reindex" Message="Are You Sure You Wish To Reindex Search Content?" Action="Reindex" Class="btn btn-danger" OnClick="@(async () => await Reindex())" ResourceKey="Reindex" />
|
||||
<br /><br />
|
||||
|
||||
@code {
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
||||
|
||||
private string _searchProvider;
|
||||
private string _enabled;
|
||||
private string _lastIndexedOn;
|
||||
private string _ignorePages;
|
||||
private string _ignoreEntities;
|
||||
private string _minimumWordLength;
|
||||
private string _ignoreWords;
|
||||
private string _enabled = "True";
|
||||
private string _lastIndexedOn = "";
|
||||
private string _ignorePages = "";
|
||||
private string _ignoreEntities = "File";
|
||||
private string _minimumWordLength = "3";
|
||||
private string _ignoreWords = "the,be,to,of,and,a,i,in,that,have,it,for,not,on,with,he,as,you,do,at,this,but,his,by,from,they,we,say,her,she,or,an,will,my,one,all,would,there,their,what,so,up,out,if,about,who,get,which,go,me,when,make,can,like,time,no,just,him,know,take,people,into,year,your,good,some,could,them,see,other,than,then,now,look,only,come,its,over,think,also,back,after,use,two,how,our,work,first,well,way,even,new,want,because,any,these,give,day,most,us";
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||
_searchProvider = SettingService.GetSetting(settings, "Search_SearchProvider", Constants.DefaultSearchProviderName);
|
||||
_enabled = SettingService.GetSetting(settings, "Search_Enabled", "True");
|
||||
_lastIndexedOn = SettingService.GetSetting(settings, "Search_LastIndexedOn", "");
|
||||
_ignorePages = SettingService.GetSetting(settings, "Search_IgnorePages", "");
|
||||
_ignoreEntities = SettingService.GetSetting(settings, "Search_IgnoreEntities", "");
|
||||
_minimumWordLength = SettingService.GetSetting(settings, "Search_MininumWordLength", "3");
|
||||
_ignoreWords = SettingService.GetSetting(settings, "Search_IgnoreWords", "");
|
||||
_enabled = SettingService.GetSetting(settings, "Search_Enabled", _enabled);
|
||||
_lastIndexedOn = SettingService.GetSetting(settings, "Search_LastIndexedOn", _lastIndexedOn);
|
||||
_ignorePages = SettingService.GetSetting(settings, "Search_IgnorePages", _ignorePages);
|
||||
_ignoreEntities = SettingService.GetSetting(settings, "Search_IgnoreEntities", _ignoreEntities);
|
||||
_minimumWordLength = SettingService.GetSetting(settings, "Search_MininumWordLength", _minimumWordLength);
|
||||
_ignoreWords = SettingService.GetSetting(settings, "Search_IgnoreWords", _ignoreWords);
|
||||
}
|
||||
|
||||
private async Task Save()
|
||||
{
|
||||
try
|
||||
{
|
||||
private async Task Save()
|
||||
{
|
||||
try
|
||||
{
|
||||
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||
settings = SettingService.SetSetting(settings, "Search_SearchProvider", _searchProvider);
|
||||
settings = SettingService.SetSetting(settings, "Search_Enabled", _enabled, true);
|
||||
settings = SettingService.SetSetting(settings, "Search_Enabled", _enabled);
|
||||
settings = SettingService.SetSetting(settings, "Search_LastIndexedOn", _lastIndexedOn, true);
|
||||
settings = SettingService.SetSetting(settings, "Search_IgnorePages", _ignorePages, true);
|
||||
settings = SettingService.SetSetting(settings, "Search_IgnoreEntities", _ignoreEntities, true);
|
||||
@ -93,10 +94,26 @@
|
||||
await SettingService.UpdateSiteSettingsAsync(settings, PageState.Site.SiteId);
|
||||
AddModuleMessage(Localizer["Success.Save"], MessageType.Success);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Saving Search Settings {Error}", ex.Message);
|
||||
AddModuleMessage(Localizer["Error.Save"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Reindex()
|
||||
{
|
||||
try
|
||||
{
|
||||
_lastIndexedOn = DateTime.MinValue.ToString();
|
||||
await Save();
|
||||
AddModuleMessage(Localizer["Message.Reindex"], MessageType.Success);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Saving Search Settings {Error}", ex.Message);
|
||||
AddModuleMessage(Localizer["Error.Save"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -7,6 +7,7 @@
|
||||
@inject ISettingService SettingService
|
||||
@inject IStringLocalizer<Index> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
@attribute [StreamRendering] // attribute allows the progress indicator to be displayed
|
||||
|
||||
<div class="search-result-container">
|
||||
<div class="row">
|
||||
@ -102,7 +103,7 @@
|
||||
|
||||
private void Search()
|
||||
{
|
||||
NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, $"page=1&q={_keywords}"));
|
||||
NavigationManager.NavigateTo(NavigateUrl(PageState.Page.Path, $"page=1&q={WebUtility.UrlEncode(_keywords)}"));
|
||||
}
|
||||
|
||||
private async Task PerformSearch()
|
||||
|
@ -10,13 +10,13 @@
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="includeentities" ResourceKey="IncludeEntities" ResourceType="@resourceType" HelpText="Comma delimited list of entities to include in the search results. By default all entities will be included.">Include Entities: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="includeentities" type="text" class="form-control" @bind="@_includeEntities" required />
|
||||
<input id="includeentities" type="text" class="form-control" @bind="@_includeEntities" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="excludeentities" ResourceKey="ExcludeEntities" ResourceType="@resourceType" HelpText="Comma delimited list of entities to exclude from search results. By default no entities will be excluded.">Exclude Entities: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="excludeentities" class="form-control" @bind="@_excludeEntities" required></input>
|
||||
<input id="excludeentities" class="form-control" @bind="@_excludeEntities" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
|
@ -207,14 +207,14 @@
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="username" HelpText="Enter the username for your SMTP account" ResourceKey="SmtpUsername">Username: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="username" class="form-control" @bind="@_smtpusername" />
|
||||
<input id="username" class="form-control" @bind="@_smtpusername" autocomplete="off"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="password" HelpText="Enter the password for your SMTP account" ResourceKey="SmtpPassword">Password: </Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<input id="password" type="@_smtppasswordtype" class="form-control" @bind="@_smtppassword" />
|
||||
<input id="password" type="@_smtppasswordtype" class="form-control" @bind="@_smtppassword" autocomplete="off"/>
|
||||
<button type="button" class="btn btn-secondary" @onclick="@ToggleSMTPPassword" tabindex="-1">@_togglesmtppassword</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -309,7 +309,7 @@
|
||||
}
|
||||
</td>
|
||||
<td>@context.Name</td>
|
||||
<td>@context.IsDefault</td>
|
||||
<td>@((context.IsDefault) ? SharedLocalizer["Yes"] : SharedLocalizer["No"])</td>
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -376,7 +376,7 @@
|
||||
<Section Name="TenantInformation" Heading="Database" ResourceKey="TenantInformation">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="tenant" HelpText="The name of the database used for the site" ResourceKey="Tenant">Database: </Label>
|
||||
<Label Class="col-sm-3" For="tenant" HelpText="The name of the database used for the site. Note that this is not the physical database name but rather the tenant name which is used within the framework to identify a database." ResourceKey="Tenant">Database: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="tenant" class="form-control" @bind="@_tenant" readonly />
|
||||
</div>
|
||||
@ -388,7 +388,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="connectionstring" HelpText="The connection information for the database" ResourceKey="ConnectionString">Connection: </Label>
|
||||
<Label Class="col-sm-3" For="connectionstring" HelpText="The name of the connection string in appsettings.json which will be used to connect to the database" ResourceKey="ConnectionString">Connection: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="connectionstring" class="form-control" @bind="@_connectionstring" readonly />
|
||||
</div>
|
||||
@ -571,7 +571,7 @@
|
||||
if (tenant != null)
|
||||
{
|
||||
_tenant = tenant.Name;
|
||||
_database = _databases.Find(item => item.DBType == tenant.DBType)?.Name;
|
||||
_database = _databases.Find(item => item.DBType == tenant.DBType && item.Name != "LocalDB")?.Name;
|
||||
_connectionstring = tenant.DBConnectionString;
|
||||
}
|
||||
}
|
||||
|
@ -109,7 +109,7 @@ else
|
||||
<hr class="app-rule" />
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="name" HelpText="Enter the name for the database" ResourceKey="TenantName">Name: </Label>
|
||||
<Label Class="col-sm-3" For="name" HelpText="Enter the name for the database. Note that this will be the tenant name which is used within the framework to identify the database." ResourceKey="TenantName">Name: </Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="name" class="form-control" @bind="@_tenantName" maxlength="100" required />
|
||||
</div>
|
||||
|
@ -83,24 +83,15 @@ else
|
||||
{
|
||||
@if (_connection != "-")
|
||||
{
|
||||
<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">
|
||||
@if (_databases != null)
|
||||
{
|
||||
<select id="databasetype" class="form-select" @bind="@_databasetype" required>
|
||||
<option value="-"><@Localizer["Type.Select"]></option>
|
||||
@foreach (var database in _databases)
|
||||
{
|
||||
<option value="@database.Name">@Localizer[@database.Name]</option>
|
||||
}
|
||||
</select>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@if (!string.IsNullOrEmpty(_tenant))
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<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>
|
||||
<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">
|
||||
<input id="tenant" class="form-control" @bind="@_tenant" readonly />
|
||||
@ -204,12 +195,12 @@ else
|
||||
{
|
||||
_connectionstring = _connections[_connection].ToString();
|
||||
_tenant = "";
|
||||
_databasetype = "-";
|
||||
_databasetype = "";
|
||||
var tenant = _tenants.FirstOrDefault(item => item.DBConnectionString == _connection);
|
||||
if (tenant != null)
|
||||
{
|
||||
_tenant = tenant.Name;
|
||||
_databasetype = _databases.FirstOrDefault(item => item.DBType == tenant.DBType).Name;
|
||||
_databasetype = _databases.FirstOrDefault(item => item.DBType == tenant.DBType && item.Name != "LocalDB").Name;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -54,6 +54,8 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
AddModuleMessage(Localizer["Disclaimer.Text"], MessageType.Warning);
|
||||
|
||||
List<Package> packages = await PackageService.GetPackagesAsync("framework", "", "", "");
|
||||
if (packages != null)
|
||||
{
|
||||
@ -97,13 +99,16 @@
|
||||
{
|
||||
try
|
||||
{
|
||||
ShowProgressIndicator();
|
||||
await PackageService.DownloadPackageAsync(packageid, version);
|
||||
await PackageService.DownloadPackageAsync(Constants.UpdaterPackageId, version);
|
||||
HideProgressIndicator();
|
||||
AddModuleMessage(Localizer["Success.Framework.Download"], MessageType.Success);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Downloading Framework Package {Error}", ex.Message);
|
||||
HideProgressIndicator();
|
||||
AddModuleMessage(Localizer["Error.Framework.Download"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,8 @@
|
||||
@inject INotificationService NotificationService
|
||||
@inject IFileService FileService
|
||||
@inject IFolderService FolderService
|
||||
@inject IJSRuntime jsRuntime
|
||||
@inject IServiceProvider ServiceProvider
|
||||
@inject IStringLocalizer<Index> Localizer
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
|
||||
@ -84,6 +86,7 @@
|
||||
<br />
|
||||
<button type="button" class="btn btn-success" @onclick="Save">@SharedLocalizer["Save"]</button>
|
||||
<button type="button" class="btn btn-secondary" @onclick="Cancel">@SharedLocalizer["Cancel"]</button>
|
||||
<button type="button" class="btn btn-danger" @onclick="Logout">@Localizer["Logout Everywhere"]</button>
|
||||
</TabPanel>
|
||||
<TabPanel Name="Profile" ResourceKey="Profile">
|
||||
<div class="container">
|
||||
@ -518,6 +521,32 @@
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Logout()
|
||||
{
|
||||
await logger.LogInformation("User Logout Everywhere For Username {Username}", PageState.User?.Username);
|
||||
|
||||
var url = NavigateUrl(""); // home page
|
||||
|
||||
if (PageState.Runtime == Shared.Runtime.Hybrid)
|
||||
{
|
||||
if (PageState.User != null)
|
||||
{
|
||||
// hybrid apps utilize an interactive logout
|
||||
await UserService.LogoutUserEverywhereAsync(PageState.User);
|
||||
var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider.GetService(typeof(IdentityAuthenticationStateProvider));
|
||||
authstateprovider.NotifyAuthenticationChanged();
|
||||
NavigationManager.NavigateTo(url, true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// post to the Logout page to complete the logout process
|
||||
var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, returnurl = url, everywhere = true };
|
||||
var interop = new Interop(jsRuntime);
|
||||
await interop.SubmitForm(Utilities.TenantUrl(PageState.Alias, "/pages/logout/"), fields);
|
||||
}
|
||||
}
|
||||
|
||||
private bool ValidateProfiles()
|
||||
{
|
||||
foreach (Profile profile in profiles)
|
||||
|
@ -14,7 +14,6 @@
|
||||
<TabPanel Name="Identity" ResourceKey="Identity">
|
||||
@if (profiles != null)
|
||||
{
|
||||
<ModuleMessage Message="@_passwordrequirements" Type="MessageType.Info" />
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="username" HelpText="A unique username for a user. Note that this field can not be modified once it is saved." ResourceKey="Username"></Label>
|
||||
@ -22,24 +21,6 @@
|
||||
<input id="username" class="form-control" @bind="@_username" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="password" HelpText="The user's password. Please choose a password which is sufficiently secure." ResourceKey="Password"></Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<input id="password" type="@_passwordtype" class="form-control" @bind="@_password" autocomplete="new-password" required />
|
||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="confirm" HelpText="Please enter the password again to confirm it matches with the value above" ResourceKey="Confirm"></Label>
|
||||
<div class="col-sm-9">
|
||||
<div class="input-group">
|
||||
<input id="confirm" type="@_passwordtype" class="form-control" @bind="@_confirm" autocomplete="new-password" required />
|
||||
<button type="button" class="btn btn-secondary" @onclick="@TogglePassword" tabindex="-1">@_togglepassword</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="email" HelpText="The email address where the user will receive notifications" ResourceKey="Email"></Label>
|
||||
<div class="col-sm-9">
|
||||
@ -123,12 +104,7 @@
|
||||
|
||||
@code {
|
||||
private bool _initialized = false;
|
||||
private string _passwordrequirements;
|
||||
private string _username = string.Empty;
|
||||
private string _password = string.Empty;
|
||||
private string _passwordtype = "password";
|
||||
private string _togglepassword = string.Empty;
|
||||
private string _confirm = string.Empty;
|
||||
private string _email = string.Empty;
|
||||
private string _displayname = string.Empty;
|
||||
private string _notify = "True";
|
||||
@ -142,8 +118,6 @@
|
||||
{
|
||||
try
|
||||
{
|
||||
_passwordrequirements = await UserService.GetPasswordRequirementsAsync(PageState.Site.SiteId);
|
||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||
profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId);
|
||||
settings = new Dictionary<string, string>();
|
||||
_initialized = true;
|
||||
@ -169,39 +143,32 @@
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_username != string.Empty && _password != string.Empty && _confirm != string.Empty && _email != string.Empty)
|
||||
if (_username != string.Empty && _email != string.Empty)
|
||||
{
|
||||
if (_password == _confirm)
|
||||
if (ValidateProfiles())
|
||||
{
|
||||
if (ValidateProfiles())
|
||||
var user = new User();
|
||||
user.SiteId = PageState.Site.SiteId;
|
||||
user.Username = _username;
|
||||
user.Password = ""; // will be auto generated
|
||||
user.Email = _email;
|
||||
user.DisplayName = string.IsNullOrWhiteSpace(_displayname) ? _username : _displayname;
|
||||
user.PhotoFileId = null;
|
||||
user.SuppressNotification = !bool.Parse(_notify);
|
||||
|
||||
user = await UserService.AddUserAsync(user);
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
var user = new User();
|
||||
user.SiteId = PageState.Site.SiteId;
|
||||
user.Username = _username;
|
||||
user.Password = _password;
|
||||
user.Email = _email;
|
||||
user.DisplayName = string.IsNullOrWhiteSpace(_displayname) ? _username : _displayname;
|
||||
user.PhotoFileId = null;
|
||||
user.SuppressNotification = !bool.Parse(_notify);
|
||||
|
||||
user = await UserService.AddUserAsync(user);
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
await SettingService.UpdateUserSettingsAsync(settings, user.UserId);
|
||||
await logger.LogInformation("User Created {User}", user);
|
||||
NavigationManager.NavigateTo(NavigateUrl());
|
||||
}
|
||||
else
|
||||
{
|
||||
await logger.LogError("Error Adding User {Username} {Email}", _username, _email);
|
||||
AddModuleMessage(Localizer["Error.User.AddCheckPass"], MessageType.Error);
|
||||
}
|
||||
await SettingService.UpdateUserSettingsAsync(settings, user.UserId);
|
||||
await logger.LogInformation("User Created {User}", user);
|
||||
NavigationManager.NavigateTo(NavigateUrl());
|
||||
}
|
||||
else
|
||||
{
|
||||
await logger.LogError("Error Adding User {Username} {Email}", _username, _email);
|
||||
AddModuleMessage(Localizer["Error.User.AddCheckPass"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AddModuleMessage(Localizer["Message.Password.NoMatch"], MessageType.Warning);
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -252,18 +219,4 @@
|
||||
var value = (string)e.Value;
|
||||
settings = SettingService.SetSetting(settings, SettingName, value);
|
||||
}
|
||||
|
||||
private void TogglePassword()
|
||||
{
|
||||
if (_passwordtype == "password")
|
||||
{
|
||||
_passwordtype = "text";
|
||||
_togglepassword = SharedLocalizer["HidePassword"];
|
||||
}
|
||||
else
|
||||
{
|
||||
_passwordtype = "password";
|
||||
_togglepassword = SharedLocalizer["ShowPassword"];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -182,13 +182,31 @@ else
|
||||
</div>
|
||||
</Section>
|
||||
<Section Name="ExternalLogin" Heading="External Login Settings" ResourceKey="ExternalLoginSettings">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<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 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["OpenID Connect"]</option>
|
||||
<option value="@AuthenticationProviderTypes.OAuth2">@Localizer["OAuth 2.0"]</option>
|
||||
<option value="" selected><@Localizer["Not Specified"]></option>
|
||||
<option value="@AuthenticationProviderTypes.OpenIDConnect">@Localizer["OIDC"]</option>
|
||||
<option value="@AuthenticationProviderTypes.OAuth2">@Localizer["OAuth2"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@ -333,12 +351,29 @@ else
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="roleclaimtype" HelpText="The name of the role claim provided by the provider" ResourceKey="RoleClaimType">Role Claim:</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">
|
||||
<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" />
|
||||
@ -435,6 +470,8 @@ else
|
||||
private string _maximumfailures;
|
||||
private string _lockoutduration;
|
||||
|
||||
private string _provider;
|
||||
private string _providerurl;
|
||||
private string _providertype;
|
||||
private string _providername;
|
||||
private string _authority;
|
||||
@ -457,6 +494,8 @@ else
|
||||
private string _nameclaimtype;
|
||||
private string _emailclaimtype;
|
||||
private string _roleclaimtype;
|
||||
private string _roleclaimmappings;
|
||||
private string _synchronizeroles;
|
||||
private string _profileclaimtypes;
|
||||
private string _domainfilter;
|
||||
private string _createusers;
|
||||
@ -500,31 +539,7 @@ else
|
||||
_maximumfailures = SettingService.GetSetting(settings, "IdentityOptions:Lockout:MaxFailedAccessAttempts", "5");
|
||||
_lockoutduration = TimeSpan.Parse(SettingService.GetSetting(settings, "IdentityOptions:Lockout:DefaultLockoutTimeSpan", "00:05:00")).TotalMinutes.ToString();
|
||||
|
||||
_providertype = SettingService.GetSetting(settings, "ExternalLogin:ProviderType", "");
|
||||
_providername = SettingService.GetSetting(settings, "ExternalLogin:ProviderName", "");
|
||||
_authority = SettingService.GetSetting(settings, "ExternalLogin:Authority", "");
|
||||
_metadataurl = SettingService.GetSetting(settings, "ExternalLogin:MetadataUrl", "");
|
||||
_authorizationurl = SettingService.GetSetting(settings, "ExternalLogin:AuthorizationUrl", "");
|
||||
_tokenurl = SettingService.GetSetting(settings, "ExternalLogin:TokenUrl", "");
|
||||
_userinfourl = SettingService.GetSetting(settings, "ExternalLogin:UserInfoUrl", "");
|
||||
_clientid = SettingService.GetSetting(settings, "ExternalLogin:ClientId", "");
|
||||
_clientsecret = SettingService.GetSetting(settings, "ExternalLogin:ClientSecret", "");
|
||||
_toggleclientsecret = SharedLocalizer["ShowPassword"];
|
||||
_authresponsetype = SettingService.GetSetting(settings, "ExternalLogin:AuthResponseType", "code");
|
||||
_scopes = SettingService.GetSetting(settings, "ExternalLogin:Scopes", "");
|
||||
_parameters = SettingService.GetSetting(settings, "ExternalLogin:Parameters", "");
|
||||
_pkce = SettingService.GetSetting(settings, "ExternalLogin:PKCE", "false");
|
||||
_redirecturl = PageState.Uri.Scheme + "://" + PageState.Alias.Name + "/signin-" + _providertype;
|
||||
_reviewclaims = SettingService.GetSetting(settings, "ExternalLogin:ReviewClaims", "false");
|
||||
_externalloginurl = Utilities.TenantUrl(PageState.Alias, "/pages/external");
|
||||
_identifierclaimtype = SettingService.GetSetting(settings, "ExternalLogin:IdentifierClaimType", "sub");
|
||||
_nameclaimtype = SettingService.GetSetting(settings, "ExternalLogin:NameClaimType", "name");
|
||||
_emailclaimtype = SettingService.GetSetting(settings, "ExternalLogin:EmailClaimType", "email");
|
||||
_roleclaimtype = SettingService.GetSetting(settings, "ExternalLogin:RoleClaimType", "");
|
||||
_profileclaimtypes = SettingService.GetSetting(settings, "ExternalLogin:ProfileClaimTypes", "");
|
||||
_domainfilter = SettingService.GetSetting(settings, "ExternalLogin:DomainFilter", "");
|
||||
_createusers = SettingService.GetSetting(settings, "ExternalLogin:CreateUsers", "true");
|
||||
_verifyusers = SettingService.GetSetting(settings, "ExternalLogin:VerifyUsers", "true");
|
||||
LoadExternalLoginSettings(settings);
|
||||
|
||||
_secret = SettingService.GetSetting(settings, "JwtOptions:Secret", "");
|
||||
_togglesecret = SharedLocalizer["ShowPassword"];
|
||||
@ -534,6 +549,39 @@ else
|
||||
}
|
||||
}
|
||||
|
||||
private void LoadExternalLoginSettings(Dictionary<string, string> settings)
|
||||
{
|
||||
_provider = SettingService.GetSetting(settings, "ExternalLogin:Provider", "<Custom>");
|
||||
_providerurl = SettingService.GetSetting(settings, "ExternalLogin:ProviderUrl", "");
|
||||
_providertype = SettingService.GetSetting(settings, "ExternalLogin:ProviderType", "");
|
||||
_providername = SettingService.GetSetting(settings, "ExternalLogin:ProviderName", "");
|
||||
_authority = SettingService.GetSetting(settings, "ExternalLogin:Authority", "");
|
||||
_metadataurl = SettingService.GetSetting(settings, "ExternalLogin:MetadataUrl", "");
|
||||
_authorizationurl = SettingService.GetSetting(settings, "ExternalLogin:AuthorizationUrl", "");
|
||||
_tokenurl = SettingService.GetSetting(settings, "ExternalLogin:TokenUrl", "");
|
||||
_userinfourl = SettingService.GetSetting(settings, "ExternalLogin:UserInfoUrl", "");
|
||||
_clientid = SettingService.GetSetting(settings, "ExternalLogin:ClientId", "");
|
||||
_clientsecret = SettingService.GetSetting(settings, "ExternalLogin:ClientSecret", "");
|
||||
_toggleclientsecret = SharedLocalizer["ShowPassword"];
|
||||
_authresponsetype = SettingService.GetSetting(settings, "ExternalLogin:AuthResponseType", "code");
|
||||
_scopes = SettingService.GetSetting(settings, "ExternalLogin:Scopes", "");
|
||||
_parameters = SettingService.GetSetting(settings, "ExternalLogin:Parameters", "");
|
||||
_pkce = SettingService.GetSetting(settings, "ExternalLogin:PKCE", "false");
|
||||
_redirecturl = PageState.Uri.Scheme + "://" + PageState.Alias.Name + "/signin-" + _providertype;
|
||||
_reviewclaims = SettingService.GetSetting(settings, "ExternalLogin:ReviewClaims", "false");
|
||||
_externalloginurl = Utilities.TenantUrl(PageState.Alias, "/pages/external");
|
||||
_identifierclaimtype = SettingService.GetSetting(settings, "ExternalLogin:IdentifierClaimType", "sub");
|
||||
_nameclaimtype = SettingService.GetSetting(settings, "ExternalLogin:NameClaimType", "name");
|
||||
_emailclaimtype = SettingService.GetSetting(settings, "ExternalLogin:EmailClaimType", "email");
|
||||
_roleclaimtype = SettingService.GetSetting(settings, "ExternalLogin:RoleClaimType", "");
|
||||
_roleclaimmappings = SettingService.GetSetting(settings, "ExternalLogin:RoleClaimMappings", "");
|
||||
_synchronizeroles = SettingService.GetSetting(settings, "ExternalLogin:SynchronizeRoles", "false");
|
||||
_profileclaimtypes = SettingService.GetSetting(settings, "ExternalLogin:ProfileClaimTypes", "");
|
||||
_domainfilter = SettingService.GetSetting(settings, "ExternalLogin:DomainFilter", "");
|
||||
_createusers = SettingService.GetSetting(settings, "ExternalLogin:CreateUsers", "true");
|
||||
_verifyusers = SettingService.GetSetting(settings, "ExternalLogin:VerifyUsers", "true");
|
||||
}
|
||||
|
||||
private async Task LoadUsersAsync(bool load)
|
||||
{
|
||||
if (load)
|
||||
@ -546,103 +594,121 @@ else
|
||||
users = users.OrderBy(u => u.User.DisplayName).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DeleteUser(UserRole UserRole)
|
||||
{
|
||||
try
|
||||
{
|
||||
var user = await UserService.GetUserAsync(UserRole.UserId, PageState.Site.SiteId);
|
||||
if (user != null)
|
||||
{
|
||||
await UserService.DeleteUserAsync(user.UserId, PageState.Site.SiteId);
|
||||
await logger.LogInformation("User Deleted {User}", UserRole.User);
|
||||
await LoadUsersAsync(true);
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Deleting User {User} {Error}", UserRole.User, ex.Message);
|
||||
AddModuleMessage(ex.Message, MessageType.Error);
|
||||
}
|
||||
}
|
||||
private async Task DeleteUser(UserRole UserRole)
|
||||
{
|
||||
try
|
||||
{
|
||||
var user = await UserService.GetUserAsync(UserRole.UserId, PageState.Site.SiteId);
|
||||
if (user != null)
|
||||
{
|
||||
await UserService.DeleteUserAsync(user.UserId, PageState.Site.SiteId);
|
||||
await logger.LogInformation("User Deleted {User}", UserRole.User);
|
||||
await LoadUsersAsync(true);
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Deleting User {User} {Error}", UserRole.User, ex.Message);
|
||||
AddModuleMessage(ex.Message, MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SaveSiteSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
var site = PageState.Site;
|
||||
site.AllowRegistration = bool.Parse(_allowregistration);
|
||||
await SiteService.UpdateSiteAsync(site);
|
||||
private async Task SaveSiteSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
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);
|
||||
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:AllowSiteLogin", _allowsitelogin, false);
|
||||
|
||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:TwoFactor", _twofactor, false);
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:CookieName", _cookiename, true);
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:CookieExpiration", _cookieexpiration, true);
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:AlwaysRemember", _alwaysremember, false);
|
||||
if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host))
|
||||
{
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:TwoFactor", _twofactor, false);
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:CookieName", _cookiename, true);
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:CookieExpiration", _cookieexpiration, true);
|
||||
settings = SettingService.SetSetting(settings, "LoginOptions:AlwaysRemember", _alwaysremember, false);
|
||||
|
||||
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequiredLength", _minimumlength, true);
|
||||
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequiredUniqueChars", _uniquecharacters, true);
|
||||
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequireDigit", _requiredigit, true);
|
||||
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequireUppercase", _requireupper, true);
|
||||
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequireLowercase", _requirelower, true);
|
||||
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequireNonAlphanumeric", _requirepunctuation, true);
|
||||
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequiredLength", _minimumlength, true);
|
||||
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequiredUniqueChars", _uniquecharacters, true);
|
||||
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequireDigit", _requiredigit, true);
|
||||
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequireUppercase", _requireupper, true);
|
||||
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequireLowercase", _requirelower, true);
|
||||
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequireNonAlphanumeric", _requirepunctuation, true);
|
||||
|
||||
settings = SettingService.SetSetting(settings, "IdentityOptions:Lockout:MaxFailedAccessAttempts", _maximumfailures, true);
|
||||
settings = SettingService.SetSetting(settings, "IdentityOptions:Lockout:DefaultLockoutTimeSpan", TimeSpan.FromMinutes(Convert.ToInt64(_lockoutduration)).ToString(), true);
|
||||
settings = SettingService.SetSetting(settings, "IdentityOptions:Lockout:MaxFailedAccessAttempts", _maximumfailures, true);
|
||||
settings = SettingService.SetSetting(settings, "IdentityOptions:Lockout:DefaultLockoutTimeSpan", TimeSpan.FromMinutes(Convert.ToInt64(_lockoutduration)).ToString(), true);
|
||||
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:ProviderType", _providertype, false);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:ProviderName", _providername, false);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:Authority", _authority, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:MetadataUrl", _metadataurl, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:AuthorizationUrl", _authorizationurl, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:TokenUrl", _tokenurl, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:UserInfoUrl", _userinfourl, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:ClientId", _clientid, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:ClientSecret", _clientsecret, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:Provider", _provider, false);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:ProviderType", _providertype, false);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:ProviderName", _providername, false);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:Authority", _authority, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:MetadataUrl", _metadataurl, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:AuthorizationUrl", _authorizationurl, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:TokenUrl", _tokenurl, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:UserInfoUrl", _userinfourl, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:ClientId", _clientid, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:ClientSecret", _clientsecret, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:AuthResponseType", _authresponsetype, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:Scopes", _scopes, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:Parameters", _parameters, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:PKCE", _pkce, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:Parameters", _parameters, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:PKCE", _pkce, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:ReviewClaims", _reviewclaims, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:IdentifierClaimType", _identifierclaimtype, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:NameClaimType", _nameclaimtype, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:EmailClaimType", _emailclaimtype, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:RoleClaimType", _roleclaimtype, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:ProfileClaimTypes", _profileclaimtypes, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:DomainFilter", _domainfilter, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:CreateUsers", _createusers, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:RoleClaimType", _roleclaimtype, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:RoleClaimMappings", _roleclaimmappings, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:SynchronizeRoles", _synchronizeroles, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:ProfileClaimTypes", _profileclaimtypes, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:DomainFilter", _domainfilter, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:CreateUsers", _createusers, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:VerifyUsers", _verifyusers, true);
|
||||
|
||||
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);
|
||||
}
|
||||
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))
|
||||
{
|
||||
SiteState.AuthorizationToken = await UserService.GetTokenAsync();
|
||||
}
|
||||
if (!string.IsNullOrEmpty(_secret))
|
||||
{
|
||||
SiteState.AuthorizationToken = await UserService.GetTokenAsync();
|
||||
}
|
||||
|
||||
AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Saving Site Settings {Error}", ex.Message);
|
||||
AddModuleMessage(Localizer["Error.SaveSiteSettings"], MessageType.Error);
|
||||
}
|
||||
AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await logger.LogError(ex, "Error Saving Site Settings {Error}", ex.Message);
|
||||
AddModuleMessage(Localizer["Error.SaveSiteSettings"], MessageType.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await ScrollToPageTop();
|
||||
}
|
||||
}
|
||||
|
||||
private void ProviderChanged(ChangeEventArgs e)
|
||||
{
|
||||
_provider = (string)e.Value;
|
||||
var provider = Shared.ExternalLoginProviders.Providers.FirstOrDefault(item => item.Name == _provider);
|
||||
if (provider != null)
|
||||
{
|
||||
LoadExternalLoginSettings(provider.Settings);
|
||||
}
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private void ProviderTypeChanged(ChangeEventArgs e)
|
||||
|
||||
private void ProviderTypeChanged(ChangeEventArgs e)
|
||||
{
|
||||
_providertype = (string)e.Value;
|
||||
if (string.IsNullOrEmpty(_providername))
|
||||
|
@ -51,25 +51,25 @@ else
|
||||
<div class="modal" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">@Header</h5>
|
||||
<form method="post" @formname="@($"ActionDialogCloseForm{Id}")" @onsubmit="DisplayModal" data-enhance>
|
||||
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||
<form class="app-form-inline" method="post" @formname="@($"ActionDialogCloseForm:{ModuleState.PageModuleId}:{Id}")" @onsubmit="DisplayModal" data-enhance>
|
||||
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">@Header</h5>
|
||||
<button type="submit" class="btn-close" aria-label="Close"></button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div class="modal-body">
|
||||
<p>@Message</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
@if (!string.IsNullOrEmpty(Action))
|
||||
{
|
||||
<form method="post" @formname="@($"ActionDialogConfirmForm{Id}")" @onsubmit="Confirm" data-enhance>
|
||||
<form method="post" @formname="@($"ActionDialogConfirmForm:{ModuleState.PageModuleId}:{Id}")" @onsubmit="Confirm" data-enhance>
|
||||
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||
<button type="submit" class="@Class">@((MarkupString)_iconSpan) @Text</button>
|
||||
</form>
|
||||
}
|
||||
<form method="post" @formname="@($"ActionDialogCancelForm{Id}")" @onsubmit="DisplayModal" data-enhance>
|
||||
<form method="post" @formname="@($"ActionDialogCancelForm:{ModuleState.PageModuleId}:{Id}")" @onsubmit="DisplayModal" data-enhance>
|
||||
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||
<button type="submit" class="btn btn-secondary">@SharedLocalizer["Cancel"]</button>
|
||||
</form>
|
||||
@ -87,7 +87,7 @@ else
|
||||
}
|
||||
else
|
||||
{
|
||||
<form method="post" @formname="@($"ActionDialogActionForm{Id}")" @onsubmit="DisplayModal" data-enhance>
|
||||
<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>
|
||||
</form>
|
||||
@ -173,6 +173,12 @@ else
|
||||
_editmode = bool.Parse(EditMode);
|
||||
}
|
||||
|
||||
Text = Localize(nameof(Text), Text);
|
||||
Header = Localize(nameof(Header), Header);
|
||||
Message = Localize(nameof(Message), Message);
|
||||
|
||||
_openText = Text;
|
||||
|
||||
if (!string.IsNullOrEmpty(IconName))
|
||||
{
|
||||
if (IconOnly)
|
||||
@ -190,15 +196,12 @@ else
|
||||
_openIconSpan = $"<span class=\"{IconName}\"></span>{(IconOnly ? "" : " ")}";
|
||||
_iconSpan = $"<span class=\"{IconName}\"></span> ";
|
||||
}
|
||||
|
||||
Text = Localize(nameof(Text), Text);
|
||||
Header = Localize(nameof(Header), Header);
|
||||
Message = Localize(nameof(Message), Message);
|
||||
|
||||
_openText = Text;
|
||||
|
||||
_permissions = (PermissionList == null) ? ModuleState.PermissionList : PermissionList;
|
||||
_authorized = IsAuthorized();
|
||||
|
||||
if (string.IsNullOrEmpty(Id)) Id = "1";
|
||||
|
||||
if (PageState.QueryString.ContainsKey("dialog"))
|
||||
{
|
||||
_visible = (PageState.QueryString["dialog"] == Id);
|
||||
|
@ -196,7 +196,7 @@
|
||||
else
|
||||
{
|
||||
FolderId = -1;
|
||||
_message = "Folder Path " + Folder + "Does Not Exist";
|
||||
_message = "Folder Path " + Folder + " Does Not Exist";
|
||||
_messagetype = MessageType.Error;
|
||||
}
|
||||
}
|
||||
@ -226,9 +226,9 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
FileId = -1; // file does not exist
|
||||
_message = "FileId " + FileId.ToString() + "Does Not Exist";
|
||||
_message = "FileId " + FileId.ToString() + " Does Not Exist";
|
||||
_messagetype = MessageType.Error;
|
||||
FileId = -1; // file does not exist
|
||||
}
|
||||
}
|
||||
|
||||
@ -314,7 +314,6 @@
|
||||
await SetImage();
|
||||
await OnSelect.InvokeAsync(FileId);
|
||||
StateHasChanged();
|
||||
|
||||
}
|
||||
|
||||
private async Task SetImage()
|
||||
@ -360,12 +359,6 @@
|
||||
}
|
||||
if (restricted == "")
|
||||
{
|
||||
if (!ShowProgress)
|
||||
{
|
||||
_uploading = true;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// upload the files
|
||||
@ -375,7 +368,21 @@
|
||||
if (PageState.Runtime == Shared.Runtime.Hybrid)
|
||||
{
|
||||
jwt = await UserService.GetTokenAsync();
|
||||
if (string.IsNullOrEmpty(jwt))
|
||||
{
|
||||
await logger.LogInformation("File Upload Failed From .NET MAUI Due To Missing Security Token. Token Options Must Be Set In User Settings.");
|
||||
_message = "Security Token Not Specified";
|
||||
_messagetype = MessageType.Error;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ShowProgress)
|
||||
{
|
||||
_uploading = true;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
await interop.UploadFiles(posturl, folder, _guid, SiteState.AntiForgeryToken, jwt);
|
||||
|
||||
// uploading is asynchronous so we need to poll to determine if uploads are completed
|
||||
@ -388,7 +395,7 @@
|
||||
|
||||
var size = Int64.Parse(uploads[upload].Split(':')[1]); // bytes
|
||||
var megabits = (size / 1048576.0) * 8; // binary conversion
|
||||
var uploadspeed = 2; // 2 Mbps (3G ranges from 300Kbps to 3Mbps)
|
||||
var uploadspeed = (PageState.Alias.Name.Contains("localhost")) ? 100 : 3; // 3 Mbps is FCC minimum for broadband upload
|
||||
var uploadtime = (megabits / uploadspeed); // seconds
|
||||
var maxattempts = 5; // polling (minimum timeout duration will be 5 seconds)
|
||||
var sleep = (int)Math.Ceiling(uploadtime / maxattempts) * 1000; // milliseconds
|
||||
|
@ -10,13 +10,16 @@
|
||||
{
|
||||
<NavLink class="ms-2" href="@NavigateUrl("admin/log")">View Details</NavLink>
|
||||
}
|
||||
@if (ModuleState.RenderMode == RenderModes.Static)
|
||||
@if (ModuleState != null)
|
||||
{
|
||||
<a href="@NavigationManager.Uri" class="btn-close" data-dismiss="alert" aria-label="close"></a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button type="button" class="btn-close" data-dismiss="alert" aria-label="close" @onclick="CloseMessage"></button>
|
||||
@if (ModuleState.RenderMode == RenderModes.Static)
|
||||
{
|
||||
<a href="@NavigationManager.Uri" class="btn-close" data-dismiss="alert" aria-label="close"></a>
|
||||
}
|
||||
else
|
||||
{
|
||||
<button type="button" class="btn-close" data-dismiss="alert" aria-label="close" @onclick="CloseMessage"></button>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
@ -452,9 +452,9 @@
|
||||
_displayPages = int.Parse(DisplayPages);
|
||||
}
|
||||
|
||||
if (PageState.QueryString.ContainsKey("page"))
|
||||
if (PageState.QueryString.ContainsKey("page") && int.TryParse(PageState.QueryString["page"], out int page))
|
||||
{
|
||||
_page = int.Parse(PageState.QueryString["page"]);
|
||||
_page = page;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -111,7 +111,7 @@
|
||||
[Parameter]
|
||||
public List<Permission> PermissionList { get; set; }
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(Permissions))
|
||||
{
|
||||
|
@ -134,6 +134,7 @@ namespace Oqtane.Modules
|
||||
|
||||
// url methods
|
||||
|
||||
// navigate url
|
||||
public string NavigateUrl()
|
||||
{
|
||||
return NavigateUrl(PageState.Page.Path);
|
||||
@ -149,24 +150,65 @@ namespace Oqtane.Modules
|
||||
return NavigateUrl(PageState.Page.Path, refresh);
|
||||
}
|
||||
|
||||
public string NavigateUrl(string path, string parameters)
|
||||
public string NavigateUrl(string path, string querystring)
|
||||
{
|
||||
return Utilities.NavigateUrl(PageState.Alias.Path, path, parameters);
|
||||
return Utilities.NavigateUrl(PageState.Alias.Path, path, querystring);
|
||||
}
|
||||
|
||||
public string NavigateUrl(string path, Dictionary<string, string> querystring)
|
||||
{
|
||||
return NavigateUrl(path, Utilities.CreateQueryString(querystring));
|
||||
}
|
||||
|
||||
public string NavigateUrl(string path, bool refresh)
|
||||
{
|
||||
return Utilities.NavigateUrl(PageState.Alias.Path, path, refresh ? "refresh" : "");
|
||||
return NavigateUrl(path, refresh ? "refresh" : "");
|
||||
}
|
||||
|
||||
public string NavigateUrl(int moduleId, string action)
|
||||
{
|
||||
return EditUrl(PageState.Page.Path, moduleId, action, "");
|
||||
}
|
||||
|
||||
public string NavigateUrl(int moduleId, string action, string querystring)
|
||||
{
|
||||
return EditUrl(PageState.Page.Path, moduleId, action, querystring);
|
||||
}
|
||||
|
||||
public string NavigateUrl(int moduleId, string action, Dictionary<string, string> querystring)
|
||||
{
|
||||
return EditUrl(PageState.Page.Path, moduleId, action, querystring);
|
||||
}
|
||||
|
||||
public string NavigateUrl(string path, int moduleId, string action)
|
||||
{
|
||||
return EditUrl(path, moduleId, action, "");
|
||||
}
|
||||
|
||||
public string NavigateUrl(string path, int moduleId, string action, string querystring)
|
||||
{
|
||||
return EditUrl(path, moduleId, action, querystring);
|
||||
}
|
||||
|
||||
public string NavigateUrl(string path, int moduleId, string action, Dictionary<string, string> querystring)
|
||||
{
|
||||
return EditUrl(path, moduleId, action, querystring);
|
||||
}
|
||||
|
||||
// edit url
|
||||
public string EditUrl(string action)
|
||||
{
|
||||
return EditUrl(ModuleState.ModuleId, action);
|
||||
}
|
||||
|
||||
public string EditUrl(string action, string parameters)
|
||||
public string EditUrl(string action, string querystring)
|
||||
{
|
||||
return EditUrl(ModuleState.ModuleId, action, parameters);
|
||||
return EditUrl(ModuleState.ModuleId, action, querystring);
|
||||
}
|
||||
|
||||
public string EditUrl(string action, Dictionary<string, string> querystring)
|
||||
{
|
||||
return EditUrl(ModuleState.ModuleId, action, querystring);
|
||||
}
|
||||
|
||||
public string EditUrl(int moduleId, string action)
|
||||
@ -174,16 +216,27 @@ namespace Oqtane.Modules
|
||||
return EditUrl(moduleId, action, "");
|
||||
}
|
||||
|
||||
public string EditUrl(int moduleId, string action, string parameters)
|
||||
public string EditUrl(int moduleId, string action, string querystring)
|
||||
{
|
||||
return EditUrl(PageState.Page.Path, moduleId, action, parameters);
|
||||
return EditUrl(PageState.Page.Path, moduleId, action, querystring);
|
||||
}
|
||||
|
||||
public string EditUrl(string path, int moduleid, string action, string parameters)
|
||||
public string EditUrl(int moduleId, string action, Dictionary<string, string> querystring)
|
||||
{
|
||||
return Utilities.EditUrl(PageState.Alias.Path, path, moduleid, action, parameters);
|
||||
return EditUrl(PageState.Page.Path, moduleId, action, querystring);
|
||||
}
|
||||
|
||||
public string EditUrl(string path, int moduleid, string action, string querystring)
|
||||
{
|
||||
return Utilities.EditUrl(PageState.Alias.Path, path, moduleid, action, querystring);
|
||||
}
|
||||
|
||||
public string EditUrl(string path, int moduleid, string action, Dictionary<string, string> querystring)
|
||||
{
|
||||
return EditUrl(path, moduleid, action, Utilities.CreateQueryString(querystring));
|
||||
}
|
||||
|
||||
// file url
|
||||
public string FileUrl(string folderpath, string filename)
|
||||
{
|
||||
return FileUrl(folderpath, filename, false);
|
||||
@ -203,6 +256,8 @@ namespace Oqtane.Modules
|
||||
return Utilities.FileUrl(PageState.Alias, fileid, download);
|
||||
}
|
||||
|
||||
// image url
|
||||
|
||||
public string ImageUrl(int fileid, int width, int height)
|
||||
{
|
||||
return ImageUrl(fileid, width, height, "");
|
||||
|
@ -1,10 +1,10 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<OutputType>Exe</OutputType>
|
||||
<Configurations>Debug;Release</Configurations>
|
||||
<Version>5.2.0</Version>
|
||||
<Version>6.0.0</Version>
|
||||
<Product>Oqtane</Product>
|
||||
<Authors>Shaun Walker</Authors>
|
||||
<Company>.NET Foundation</Company>
|
||||
@ -12,7 +12,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/v5.2.0</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.0</PackageReleaseNotes>
|
||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<RootNamespace>Oqtane</RootNamespace>
|
||||
@ -22,11 +22,10 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.7" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="8.0.7" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.7" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Localization" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
@ -13,13 +12,13 @@ using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
||||
using Microsoft.AspNetCore.Localization;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.JSInterop;
|
||||
using Oqtane.Documentation;
|
||||
using Oqtane.Models;
|
||||
using Oqtane.Modules;
|
||||
using Oqtane.Services;
|
||||
using Oqtane.Shared;
|
||||
using Oqtane.UI;
|
||||
|
||||
namespace Oqtane.Client
|
||||
@ -258,7 +257,7 @@ namespace Oqtane.Client
|
||||
var jsRuntime = serviceProvider.GetRequiredService<IJSRuntime>();
|
||||
var interop = new Interop(jsRuntime);
|
||||
var localizationCookie = await interop.GetCookie(CookieRequestCultureProvider.DefaultCookieName);
|
||||
var culture = CookieRequestCultureProvider.ParseCookieValue(localizationCookie)?.UICultures?[0].Value;
|
||||
var culture = CookieRequestCultureProvider.ParseCookieValue(localizationCookie)?.UICulture.Name;
|
||||
var localizationService = serviceProvider.GetRequiredService<ILocalizationService>();
|
||||
var cultures = await localizationService.GetCulturesAsync(false);
|
||||
|
||||
|
@ -183,4 +183,7 @@
|
||||
<data name="Template" xml:space="preserve">
|
||||
<value>Select a site template</value>
|
||||
</data>
|
||||
<data name="Message.Username.Invalid" xml:space="preserve">
|
||||
<value>The Username Provided Does Not Meet The System Requirement, It Can Only Contains Letters Or Digits.</value>
|
||||
</data>
|
||||
</root>
|
@ -150,14 +150,11 @@
|
||||
<data name="Name.HelpText" xml:space="preserve">
|
||||
<value>Enter the folder name</value>
|
||||
</data>
|
||||
<data name="Permissions.HelpText" xml:space="preserve">
|
||||
<value>Select the permissions you want for the folder</value>
|
||||
</data>
|
||||
<data name="Parent.Text" xml:space="preserve">
|
||||
<value>Parent: </value>
|
||||
</data>
|
||||
<data name="Permissions.Text" xml:space="preserve">
|
||||
<value>Permissions: </value>
|
||||
<data name="Permissions.Heading" xml:space="preserve">
|
||||
<value>Permissions</value>
|
||||
</data>
|
||||
<data name="DeleteFolder.Header" xml:space="preserve">
|
||||
<value>Delete Folder</value>
|
||||
@ -198,4 +195,7 @@
|
||||
<data name="Message.Folder.Duplicate" xml:space="preserve">
|
||||
<value>Folder Name Specified Already Exists In Parent</value>
|
||||
</data>
|
||||
<data name="Settings.Heading" xml:space="preserve">
|
||||
<value>Settings</value>
|
||||
</data>
|
||||
</root>
|
@ -139,7 +139,7 @@
|
||||
<value>Ignore Entities: </value>
|
||||
</data>
|
||||
<data name="IgnoreEntities.HelpText" xml:space="preserve">
|
||||
<value>Comma delimited list of entities which should be ignored</value>
|
||||
<value>Comma delimited list of entities which should be ignored. By default File entities are ignored.</value>
|
||||
</data>
|
||||
<data name="MinimumWordLength.Text" xml:space="preserve">
|
||||
<value>Word Length: </value>
|
||||
@ -154,7 +154,7 @@
|
||||
<value>Comma delimited list of words which should be ignored</value>
|
||||
</data>
|
||||
<data name="Success.Save" xml:space="preserve">
|
||||
<value>Search Settings Saved Successfully</value>
|
||||
<value>Search Settings Saved Successfully. You Will Need Reindex For Your Changes To Be Reflected In The Search Results.</value>
|
||||
</data>
|
||||
<data name="Error.Save" xml:space="preserve">
|
||||
<value>Error Saving Search Settings</value>
|
||||
@ -165,4 +165,16 @@
|
||||
<data name="SearchProvider.Text" xml:space="preserve">
|
||||
<value>Search Provider:</value>
|
||||
</data>
|
||||
<data name="Message.Reindex" xml:space="preserve">
|
||||
<value>The search index will be rebuilt for this site. Please be patient during the reindexing process.</value>
|
||||
</data>
|
||||
<data name="Reindex.Text" xml:space="preserve">
|
||||
<value>Reindex</value>
|
||||
</data>
|
||||
<data name="Reindex.Header" xml:space="preserve">
|
||||
<value>Reindex</value>
|
||||
</data>
|
||||
<data name="Reindex.Message" xml:space="preserve">
|
||||
<value>Are You Sure You Wish To Reindex Search Content?</value>
|
||||
</data>
|
||||
</root>
|
@ -163,7 +163,7 @@
|
||||
<value>Enter the site name</value>
|
||||
</data>
|
||||
<data name="Tenant.HelpText" xml:space="preserve">
|
||||
<value>The name of the database used for the site</value>
|
||||
<value>The name of the database used for the site. Note that this is not the physical database name but rather the tenant name which is used within the framework to identify a database.</value>
|
||||
</data>
|
||||
<data name="Aliases.HelpText" xml:space="preserve">
|
||||
<value>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).</value>
|
||||
@ -307,7 +307,7 @@
|
||||
<value>Type:</value>
|
||||
</data>
|
||||
<data name="ConnectionString.HelpText" xml:space="preserve">
|
||||
<value>The connection information for the database</value>
|
||||
<value>The name of the connection string in appsettings.json which will be used to connect to the database</value>
|
||||
</data>
|
||||
<data name="Database.HelpText" xml:space="preserve">
|
||||
<value>The type of database</value>
|
||||
|
@ -187,7 +187,7 @@
|
||||
<value>Select the database for the site</value>
|
||||
</data>
|
||||
<data name="TenantName.HelpText" xml:space="preserve">
|
||||
<value>Enter the name for the database</value>
|
||||
<value>Enter the name for the database. Note that this will be the tenant name which is used within the framework to identify the database.</value>
|
||||
</data>
|
||||
<data name="DatabaseType.HelpText" xml:space="preserve">
|
||||
<value>Select the database type</value>
|
||||
|
@ -150,4 +150,7 @@
|
||||
<data name="Localhost.Text" xml:space="preserve">
|
||||
<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. Also Note That The System Update Capability Is Not Recommended When Using Microsoft Azure Due To The Limitations Of That Environment. </value>
|
||||
</data>
|
||||
</root>
|
@ -243,4 +243,7 @@
|
||||
<data name="NoNotificationsSent.Text" xml:space="preserve">
|
||||
<value>No notifications have been sent</value>
|
||||
</data>
|
||||
<data name="Logout Everywhere" xml:space="preserve">
|
||||
<value>Logout Everywhere</value>
|
||||
</data>
|
||||
</root>
|
@ -117,12 +117,6 @@
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="Error.User.AddCheckPass" xml:space="preserve">
|
||||
<value>Error Adding User. Please Ensure Password Meets Complexity Requirements And Username And Email Is Not Already In Use.</value>
|
||||
</data>
|
||||
<data name="Message.Password.NoMatch" xml:space="preserve">
|
||||
<value>Passwords Entered Do Not Match</value>
|
||||
</data>
|
||||
<data name="Error.User.Add" xml:space="preserve">
|
||||
<value>Error Adding User</value>
|
||||
</data>
|
||||
@ -133,17 +127,11 @@
|
||||
<value>Identity</value>
|
||||
</data>
|
||||
<data name="Message.Required.ProfileInfo" xml:space="preserve">
|
||||
<value>You Must Provide A Username, Password, Email Address And All Required Profile Information</value>
|
||||
<value>You Must Provide A Username, Email Address And All Required Profile Information</value>
|
||||
</data>
|
||||
<data name="Message.Username.Exists" xml:space="preserve">
|
||||
<value>Username Already Exists</value>
|
||||
</data>
|
||||
<data name="Confirm.HelpText" xml:space="preserve">
|
||||
<value>Please enter the password again to confirm it matches with the value above</value>
|
||||
</data>
|
||||
<data name="Confirm.Text" xml:space="preserve">
|
||||
<value>Confirm Password:</value>
|
||||
</data>
|
||||
<data name="DisplayName.HelpText" xml:space="preserve">
|
||||
<value>The full name of the user</value>
|
||||
</data>
|
||||
@ -156,21 +144,12 @@
|
||||
<data name="Email.Text" xml:space="preserve">
|
||||
<value>Email:</value>
|
||||
</data>
|
||||
<data name="Password.HelpText" xml:space="preserve">
|
||||
<value>The user's password. Please choose a password which is sufficiently secure.</value>
|
||||
</data>
|
||||
<data name="Password.Text" xml:space="preserve">
|
||||
<value>Password:</value>
|
||||
</data>
|
||||
<data name="Username.HelpText" xml:space="preserve">
|
||||
<value>A unique username for a user. Note that this field can not be modified once it is saved.</value>
|
||||
</data>
|
||||
<data name="Username.Text" xml:space="preserve">
|
||||
<value>Username:</value>
|
||||
</data>
|
||||
<data name="Password.Placeholder" xml:space="preserve">
|
||||
<value>Password</value>
|
||||
</data>
|
||||
<data name="Notify.HelpText" xml:space="preserve">
|
||||
<value>Indicate if new users should receive an email notification</value>
|
||||
</data>
|
||||
|
@ -385,10 +385,22 @@
|
||||
<value>Parameters:</value>
|
||||
</data>
|
||||
<data name="RoleClaimType.HelpText" xml:space="preserve">
|
||||
<value>Optionally provide the type name of the role claim provided by the identity provider. These roles will be used in addition to any internal user roles assigned within the site.</value>
|
||||
<value>Optionally provide the type name of the roles claim provided by the identity provider (the standard default is 'roles'). If role names from the identity provider do not exactly match your site role names, please use the Role Claim Mappings.</value>
|
||||
</data>
|
||||
<data name="RoleClaimType.Text" xml:space="preserve">
|
||||
<value>Role Claim:</value>
|
||||
<value>Roles Claim:</value>
|
||||
</data>
|
||||
<data name="RoleClaimMappings.HelpText" xml:space="preserve">
|
||||
<value>Optionally provide a comma delimited list of role names provided by the identity provider, as well as mappings to your site roles. For example if the identity provider includes an 'Admin' role name and you want it to map to the 'Administrators' site role you should specify 'Admin:Administrators'.</value>
|
||||
</data>
|
||||
<data name="RoleClaimMappings.Text" xml:space="preserve">
|
||||
<value>Role Claim Mappings:</value>
|
||||
</data>
|
||||
<data name="SynchronizeRoles.HelpText" xml:space="preserve">
|
||||
<value>This option will add or remove role assignments so that the site roles exactly match the roles provided by the identity provider for a user</value>
|
||||
</data>
|
||||
<data name="SynchronizeRoles.Text" xml:space="preserve">
|
||||
<value>Synchronize Roles?</value>
|
||||
</data>
|
||||
<data name="ProfileClaimTypes.HelpText" xml:space="preserve">
|
||||
<value>Optionally provide a comma delimited list of user profile claim type names provided by the identity provider, as well as mappings to your user profile definition. For example if the identity provider includes a 'given_name' claim and you have a 'FirstName' user profile definition you should specify 'given_name:FirstName'.</value>
|
||||
@ -459,13 +471,28 @@
|
||||
<data name="ReviewClaims.Text" xml:space="preserve">
|
||||
<value>Review Claims?</value>
|
||||
</data>
|
||||
<data name="ReviewClaims.HelpText" xml:space="preserve">
|
||||
<data name="ReviewClaims.HelpText" xml:space="preserve">
|
||||
<value>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.</value>
|
||||
</data>
|
||||
<data name="NameClaimType.HelpText" xml:space="preserve">
|
||||
<value>Optionally specify the type name of the user's name claim provided by the identity provider. The typical value is 'name'.</value>
|
||||
</data>
|
||||
<data name="NameClaimType.Text" xml:space="preserve">
|
||||
<data name="NameClaimType.Text" xml:space="preserve">
|
||||
<value>Name Claim:</value>
|
||||
</data>
|
||||
<data name="Provider.HelpText" xml:space="preserve">
|
||||
<value>Select the external login provider</value>
|
||||
</data>
|
||||
<data name="Provider.Text" xml:space="preserve">
|
||||
<value>Provider:</value>
|
||||
</data>
|
||||
<data name="Info" xml:space="preserve">
|
||||
<value>Info</value>
|
||||
</data>
|
||||
<data name="OAuth2" xml:space="preserve">
|
||||
<value>OAuth 2.0</value>
|
||||
</data>
|
||||
<data name="OIDC" xml:space="preserve">
|
||||
<value>OpenID Connect (OIDC)</value>
|
||||
</data>
|
||||
</root>
|
@ -213,6 +213,9 @@
|
||||
<data name="Reset" xml:space="preserve">
|
||||
<value>Reset</value>
|
||||
</data>
|
||||
<data name="Search Settings" xml:space="preserve">
|
||||
<value>Search Settings</value>
|
||||
</data>
|
||||
<data name="Search" xml:space="preserve">
|
||||
<value>Search</value>
|
||||
</data>
|
||||
|
@ -75,5 +75,10 @@ namespace Oqtane.Services
|
||||
{
|
||||
return await GetByteArrayAsync($"{Apiurl}/download/{fileId}");
|
||||
}
|
||||
|
||||
public async Task UnzipFileAsync(int fileId)
|
||||
{
|
||||
await PutAsync($"{Apiurl}/unzip/{fileId}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -92,5 +92,13 @@ namespace Oqtane.Services
|
||||
/// </param>
|
||||
/// <returns></returns>
|
||||
Task<List<File>> GetFilesAsync(int siteId, string folderPath);
|
||||
|
||||
/// <summary>
|
||||
/// Unzips the contents of a zip file
|
||||
/// </summary>
|
||||
/// <param name="fileId">Reference to the <see cref="File"/></param>
|
||||
/// </param>
|
||||
/// <returns></returns>
|
||||
Task UnzipFileAsync(int fileId);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,17 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Oqtane.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Service to set localization cookie
|
||||
/// </summary>
|
||||
public interface ILocalizationCookieService
|
||||
{
|
||||
/// <summary>
|
||||
/// Set the localization cookie
|
||||
/// </summary>
|
||||
/// <param name="culture"></param>
|
||||
/// <returns></returns>
|
||||
Task SetLocalizationCookieAsync(string culture);
|
||||
}
|
||||
}
|
@ -179,6 +179,17 @@ namespace Oqtane.Services
|
||||
/// <returns></returns>
|
||||
Task UpdateSettingsAsync(Dictionary<string, string> settings, string entityName, int entityId);
|
||||
|
||||
/// <summary>
|
||||
/// Updates setting for a given entityName and Id
|
||||
/// </summary>
|
||||
/// <param name="entityName"></param>
|
||||
/// <param name="entityId"></param>
|
||||
/// <param name="settingName"></param>
|
||||
/// <param name="settingValue"></param>
|
||||
/// <param name="isPrivate"></param>
|
||||
/// <returns></returns>
|
||||
Task AddOrUpdateSettingAsync(string entityName, int entityId, string settingName, string settingValue, bool isPrivate);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a specific setting
|
||||
/// </summary>
|
||||
|
@ -75,6 +75,13 @@ namespace Oqtane.Services
|
||||
/// <returns></returns>
|
||||
Task LogoutUserAsync(User user);
|
||||
|
||||
/// <summary>
|
||||
/// Logout a <see cref="User"/>
|
||||
/// </summary>
|
||||
/// <param name="user"></param>
|
||||
/// <returns></returns>
|
||||
Task LogoutUserEverywhereAsync(User user);
|
||||
|
||||
/// <summary>
|
||||
/// Update e-mail verification status of a user.
|
||||
/// </summary>
|
||||
@ -106,6 +113,15 @@ namespace Oqtane.Services
|
||||
/// <returns></returns>
|
||||
Task<User> VerifyTwoFactorAsync(User user, string token);
|
||||
|
||||
/// <summary>
|
||||
/// Validate identity user info.
|
||||
/// </summary>
|
||||
/// <param name="username"></param>
|
||||
/// <param name="email"></param>
|
||||
/// <param name="password"></param>
|
||||
/// <returns></returns>
|
||||
Task<UserValidateResult> ValidateUserAsync(string username, string email, string password);
|
||||
|
||||
/// <summary>
|
||||
/// Validate a users password against the password policy
|
||||
/// </summary>
|
||||
|
18
Oqtane.Client/Services/LocalizationCookieService.cs
Normal file
18
Oqtane.Client/Services/LocalizationCookieService.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using Oqtane.Documentation;
|
||||
using Oqtane.Shared;
|
||||
|
||||
namespace Oqtane.Services
|
||||
{
|
||||
[PrivateApi("Don't show in the documentation, as everything should use the Interface")]
|
||||
public class LocalizationCookieService : ServiceBase, ILocalizationCookieService
|
||||
{
|
||||
public LocalizationCookieService(HttpClient http, SiteState siteState) : base(http, siteState) { }
|
||||
|
||||
public Task SetLocalizationCookieAsync(string culture)
|
||||
{
|
||||
return Task.CompletedTask; // only used in server side rendering
|
||||
}
|
||||
}
|
||||
}
|
@ -4,7 +4,6 @@ using System.Net.Http;
|
||||
using System.Net.Http.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using Oqtane.Shared;
|
||||
|
||||
namespace Oqtane.Services
|
||||
@ -28,9 +27,9 @@ namespace Oqtane.Services
|
||||
private HttpClient GetHttpClient(string AuthorizationToken)
|
||||
{
|
||||
var httpClient = _httpClientFactory.CreateClient("Remote");
|
||||
if (!httpClient.DefaultRequestHeaders.Contains(HeaderNames.Authorization) && !string.IsNullOrEmpty(AuthorizationToken))
|
||||
if (!httpClient.DefaultRequestHeaders.Contains("Authorization") && !string.IsNullOrEmpty(AuthorizationToken))
|
||||
{
|
||||
httpClient.DefaultRequestHeaders.Add(HeaderNames.Authorization, "Bearer " + AuthorizationToken);
|
||||
httpClient.DefaultRequestHeaders.Add("Authorization", "Bearer " + AuthorizationToken);
|
||||
}
|
||||
return httpClient;
|
||||
}
|
||||
|
@ -35,6 +35,7 @@ namespace Oqtane.Services
|
||||
if (_factory != null)
|
||||
{
|
||||
var client = _factory.CreateClient("oqtane");
|
||||
client.BaseAddress = new Uri(_siteState.Alias.Protocol + _siteState.Alias.Name);
|
||||
if (!client.DefaultRequestHeaders.Contains(Constants.AntiForgeryTokenHeaderName) && _siteState != null && !string.IsNullOrEmpty(_siteState.AntiForgeryToken))
|
||||
{
|
||||
client.DefaultRequestHeaders.Add(Constants.AntiForgeryTokenHeaderName, _siteState.AntiForgeryToken);
|
||||
|
@ -12,7 +12,7 @@ namespace Oqtane.Services
|
||||
[PrivateApi("Don't show in the documentation, as everything should use the Interface")]
|
||||
public class SettingService : ServiceBase, ISettingService
|
||||
{
|
||||
public SettingService(HttpClient http, SiteState siteState) : base(http, siteState) { }
|
||||
public SettingService(HttpClient http, SiteState siteState) : base(http, siteState) {}
|
||||
|
||||
private string Apiurl => CreateApiUrl("Setting");
|
||||
|
||||
@ -134,7 +134,7 @@ namespace Oqtane.Services
|
||||
public async Task<Dictionary<string, string>> GetSettingsAsync(string entityName, int entityId)
|
||||
{
|
||||
var dictionary = new Dictionary<string, string>();
|
||||
var settings = await GetJsonAsync<List<Setting>>($"{Apiurl}?entityname={entityName}&entityid={entityId}");
|
||||
var settings = await GetSettingsAsync(entityName, entityId, "");
|
||||
if (settings != null)
|
||||
{
|
||||
foreach (Setting setting in settings.OrderBy(item => item.SettingName).ToList())
|
||||
@ -147,7 +147,7 @@ namespace Oqtane.Services
|
||||
|
||||
public async Task UpdateSettingsAsync(Dictionary<string, string> settings, string entityName, int entityId)
|
||||
{
|
||||
var settingsList = await GetJsonAsync<List<Setting>>($"{Apiurl}?entityname={entityName}&entityid={entityId}");
|
||||
var settingsList = await GetSettingsAsync(entityName, entityId, "");
|
||||
|
||||
foreach (KeyValuePair<string, string> kvp in settings)
|
||||
{
|
||||
@ -192,14 +192,14 @@ namespace Oqtane.Services
|
||||
}
|
||||
}
|
||||
|
||||
public async Task AddOrUpdateSettingAsync(string entityName, int entityId, string settingName, string settingValue, bool isPrivate)
|
||||
{
|
||||
await PutAsync($"{Apiurl}/{entityName}/{entityId}/{settingName}/{settingValue}/{isPrivate}");
|
||||
}
|
||||
|
||||
public async Task DeleteSettingAsync(string entityName, int entityId, string settingName)
|
||||
{
|
||||
var settings = await GetJsonAsync<List<Setting>>($"{Apiurl}?entityname={entityName}&entityid={entityId}");
|
||||
var setting = settings.FirstOrDefault(item => item.SettingName == settingName);
|
||||
if (setting != null)
|
||||
{
|
||||
await DeleteAsync($"{Apiurl}/{setting.SettingId}/{entityName}");
|
||||
}
|
||||
await DeleteAsync($"{Apiurl}/{entityName}/{entityId}/{settingName}");
|
||||
}
|
||||
|
||||
public async Task<List<Setting>> GetSettingsAsync(string entityName, int entityId, string settingName)
|
||||
|
@ -61,10 +61,14 @@ namespace Oqtane.Services
|
||||
|
||||
public async Task LogoutUserAsync(User user)
|
||||
{
|
||||
// best practices recommend post is preferrable to get for logout
|
||||
await PostJsonAsync($"{Apiurl}/logout", user);
|
||||
}
|
||||
|
||||
public async Task LogoutUserEverywhereAsync(User user)
|
||||
{
|
||||
await PostJsonAsync($"{Apiurl}/logouteverywhere", user);
|
||||
}
|
||||
|
||||
public async Task<User> VerifyEmailAsync(User user, string token)
|
||||
{
|
||||
return await PostJsonAsync<User>($"{Apiurl}/verify?token={token}", user);
|
||||
@ -85,6 +89,11 @@ namespace Oqtane.Services
|
||||
return await PostJsonAsync<User>($"{Apiurl}/twofactor?token={token}", user);
|
||||
}
|
||||
|
||||
public async Task<UserValidateResult> ValidateUserAsync(string username, string email, string password)
|
||||
{
|
||||
return await GetJsonAsync<UserValidateResult>($"{Apiurl}/validateuser?username={WebUtility.UrlEncode(username)}&email={WebUtility.UrlEncode(email)}&password={WebUtility.UrlEncode(password)}");
|
||||
}
|
||||
|
||||
public async Task<bool> ValidatePasswordAsync(string password)
|
||||
{
|
||||
return await GetJsonAsync<bool>($"{Apiurl}/validate/{WebUtility.UrlEncode(password)}");
|
||||
|
@ -9,7 +9,7 @@
|
||||
<div class="row flex-xl-nowrap gx-0">
|
||||
<div class="sidebar">
|
||||
<nav class="navbar">
|
||||
<Logo />
|
||||
<Logo UseSiteNameAsFallback="true" />
|
||||
<Menu Orientation="Vertical" />
|
||||
</nav>
|
||||
</div>
|
||||
@ -41,9 +41,7 @@
|
||||
Integrity = "sha512-jnSuA4Ss2PkkikSOLtYs8BlYIeeIK1h99ty4YfvRPAlzr377vr3CXDb7sb7eEEBYjDtcYj+AjBH3FLv5uSJuXg==",
|
||||
CrossOrigin = "anonymous" },
|
||||
new Resource { ResourceType = ResourceType.Stylesheet, Url = ThemePath() + "Theme.css" },
|
||||
new Resource { ResourceType = ResourceType.Script, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/js/bootstrap.bundle.min.js",
|
||||
Integrity = "sha512-7Pi/otdlbbCR+LnW+F7PwFcSDJOuUJB3OxtEHbg4vSMvzvJjde4Po1v4BR9Gdc9aXNUNFVUY+SK51wWT8WF0Gg==",
|
||||
CrossOrigin = "anonymous", Location = ResourceLocation.Body },
|
||||
new Resource { ResourceType = ResourceType.Script, Url = Constants.BootstrapScriptUrl, Integrity = Constants.BootstrapScriptIntegrity, CrossOrigin = "anonymous", Location = ResourceLocation.Body },
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -11,9 +11,6 @@ using System.Net;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using Oqtane.UI;
|
||||
|
||||
// ReSharper disable UnassignedGetOnlyAutoProperty
|
||||
// ReSharper disable MemberCanBePrivate.Global
|
||||
|
||||
namespace Oqtane.Themes.Controls
|
||||
{
|
||||
public class ModuleActionsBase : ComponentBase
|
||||
@ -92,20 +89,21 @@ namespace Oqtane.Themes.Controls
|
||||
return actionList;
|
||||
}
|
||||
|
||||
private async Task<string> EditUrlAsync(string url, int moduleId, string import)
|
||||
{
|
||||
await Task.Yield();
|
||||
return Utilities.EditUrl(PageState.Alias.Path, PageState.Page.Path, moduleId, import, "");
|
||||
}
|
||||
|
||||
protected async Task ModuleAction(ActionViewModel action)
|
||||
{
|
||||
if (PageState.EditMode && UserSecurity.IsAuthorized(PageState.User, PermissionNames.Edit, ModuleState.PermissionList))
|
||||
{
|
||||
PageModule pagemodule = await PageModuleService.GetPageModuleAsync(ModuleState.PageModuleId);
|
||||
|
||||
string url = Utilities.NavigateUrl(PageState.Alias.Path, PageState.Page.Path, "edit=true&refresh");
|
||||
var url = NavigationManager.Uri.Substring(NavigationManager.BaseUri.Length - 1);
|
||||
if (!url.Contains("edit="))
|
||||
{
|
||||
url += (!url.Contains("?") ? "?" : "&") + "edit=true";
|
||||
}
|
||||
if (!url.Contains("refresh="))
|
||||
{
|
||||
url += (!url.Contains("?") ? "?" : "&") + "refresh=true";
|
||||
}
|
||||
|
||||
var pagemodule = await PageModuleService.GetPageModuleAsync(ModuleState.PageModuleId);
|
||||
if (action.Action != null)
|
||||
{
|
||||
url = await action.Action(url, pagemodule);
|
||||
@ -115,31 +113,10 @@ namespace Oqtane.Themes.Controls
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<string> MoveToPane(string url, string newPane, PageModule pagemodule)
|
||||
private Task<string> Settings(string url, PageModule pagemodule)
|
||||
{
|
||||
string oldPane = pagemodule.Pane;
|
||||
pagemodule.Pane = newPane;
|
||||
pagemodule.Order = int.MaxValue; // add to bottom of pane
|
||||
await PageModuleService.UpdatePageModuleAsync(pagemodule);
|
||||
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane);
|
||||
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, oldPane);
|
||||
return url;
|
||||
}
|
||||
|
||||
private async Task<string> DeleteModule(string url, PageModule pagemodule)
|
||||
{
|
||||
pagemodule.IsDeleted = true;
|
||||
await PageModuleService.UpdatePageModuleAsync(pagemodule);
|
||||
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane);
|
||||
return url;
|
||||
}
|
||||
|
||||
private async Task<string> Settings(string url, PageModule pagemodule)
|
||||
{
|
||||
await Task.Yield();
|
||||
var returnurl = Utilities.NavigateUrl(PageState.Alias.Path, PageState.Page.Path, "edit=true");
|
||||
url = Utilities.EditUrl(PageState.Alias.Path, PageState.Page.Path, pagemodule.ModuleId, "Settings", "returnurl=" + WebUtility.UrlEncode(returnurl));
|
||||
return url;
|
||||
url = Utilities.EditUrl(PageState.Alias.Path, PageState.Page.Path, pagemodule.ModuleId, "Settings", "returnurl=" + WebUtility.UrlEncode(url));
|
||||
return Task.FromResult(url);
|
||||
}
|
||||
|
||||
private async Task<string> Publish(string url, PageModule pagemodule)
|
||||
@ -174,6 +151,20 @@ namespace Oqtane.Themes.Controls
|
||||
return url;
|
||||
}
|
||||
|
||||
private async Task<string> DeleteModule(string url, PageModule pagemodule)
|
||||
{
|
||||
pagemodule.IsDeleted = true;
|
||||
await PageModuleService.UpdatePageModuleAsync(pagemodule);
|
||||
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane);
|
||||
return url;
|
||||
}
|
||||
|
||||
private Task<string> EditUrlAsync(string url, int moduleId, string import)
|
||||
{
|
||||
url = Utilities.EditUrl(PageState.Alias.Path, PageState.Page.Path, moduleId, import, "returnurl=" + WebUtility.UrlEncode(url));
|
||||
return Task.FromResult(url);
|
||||
}
|
||||
|
||||
private async Task<string> MoveTop(string url, PageModule pagemodule)
|
||||
{
|
||||
pagemodule.Order = 0;
|
||||
@ -206,6 +197,17 @@ namespace Oqtane.Themes.Controls
|
||||
return url;
|
||||
}
|
||||
|
||||
private async Task<string> MoveToPane(string url, string newPane, PageModule pagemodule)
|
||||
{
|
||||
string oldPane = pagemodule.Pane;
|
||||
pagemodule.Pane = newPane;
|
||||
pagemodule.Order = int.MaxValue; // add to bottom of pane
|
||||
await PageModuleService.UpdatePageModuleAsync(pagemodule);
|
||||
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, pagemodule.Pane);
|
||||
await PageModuleService.UpdatePageModuleOrderAsync(pagemodule.PageId, oldPane);
|
||||
return url;
|
||||
}
|
||||
|
||||
public class ActionViewModel
|
||||
{
|
||||
public string Icon { get; set; }
|
||||
|
@ -134,8 +134,7 @@
|
||||
if (PageState.User != null)
|
||||
{
|
||||
// preserve edit mode for authenticated users
|
||||
var userSettings = new Dictionary<string, string> { { "CP-editmode", (PageState.EditMode) ? PageState.Page.PageId.ToString() : "-1" } };
|
||||
await SettingService.UpdateUserSettingsAsync(userSettings, PageState.User.UserId);
|
||||
await SettingService.AddOrUpdateSettingAsync(EntityNames.User, PageState.User.UserId, "CP-editmode", (PageState.EditMode) ? PageState.Page.PageId.ToString() : "-1", false);
|
||||
}
|
||||
|
||||
// preserve other querystring parameters
|
||||
@ -148,8 +147,7 @@
|
||||
{
|
||||
if (PageState.Page.IsPersonalizable && PageState.User != null && UserSecurity.IsAuthorized(PageState.User, RoleNames.Registered))
|
||||
{
|
||||
PageState.EditMode = true;
|
||||
NavigationManager.NavigateTo(NavigateUrl(page.Path, "edit=" + ((PageState.EditMode) ? "true" : "false")));
|
||||
NavigationManager.NavigateTo(NavigateUrl(page.Path, "edit=" + PageState.EditMode.ToString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -331,7 +331,7 @@
|
||||
if (_pageId != "-")
|
||||
{
|
||||
_modules = await ModuleService.GetModulesAsync(PageState.Page.SiteId);
|
||||
_modules = _modules.Where(module => module.PageId == int.Parse(_pageId) &&
|
||||
_modules = _modules.Where(module => module.PageId == int.Parse(_pageId) && module.IsDeleted == false &&
|
||||
UserSecurity.IsAuthorized(PageState.User, PermissionNames.View, module.PermissionList) &&
|
||||
(_moduleType == "add" || module.ModuleDefinition.IsPortable))
|
||||
.ToList();
|
||||
@ -454,7 +454,7 @@
|
||||
{
|
||||
foreach (var permission in PageState.Page.PermissionList.Where(item => item.PermissionName == pagePermission))
|
||||
{
|
||||
permissions.Add(new Permission { SiteId = siteId, EntityName = EntityNames.Module, PermissionName = modulePermission, RoleId = permission.RoleId, UserId = permission.UserId, IsAuthorized = permission.IsAuthorized });
|
||||
permissions.Add(new Permission { SiteId = siteId, EntityName = EntityNames.Module, PermissionName = modulePermission, RoleName = permission.RoleName, UserId = permission.UserId, IsAuthorized = permission.IsAuthorized });
|
||||
}
|
||||
return permissions;
|
||||
}
|
||||
|
@ -1,10 +1,9 @@
|
||||
@using System.Globalization
|
||||
@using Microsoft.AspNetCore.Localization
|
||||
@using Microsoft.AspNetCore.Http
|
||||
@using Oqtane.Models
|
||||
@namespace Oqtane.Themes.Controls
|
||||
@inherits ThemeControlBase
|
||||
@inject ILanguageService LanguageService
|
||||
@inject ILocalizationCookieService LocalizationCookieService
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
@if (_supportedCultures?.Count() > 1)
|
||||
@ -22,7 +21,7 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
<a class="dropdown-item @(CultureInfo.CurrentUICulture.Name == culture.Name ? "active" : String.Empty)" href="@NavigateUrl(PageState.Page.Path, "culture=" + culture.Name)">@culture.DisplayName</a>
|
||||
<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>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
@ -38,25 +37,20 @@
|
||||
[Parameter]
|
||||
public string ButtonClass { get; set; } = "btn-outline-secondary";
|
||||
|
||||
[CascadingParameter]
|
||||
HttpContext HttpContext { get; set; }
|
||||
|
||||
protected override void OnParametersSet()
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
MenuAlignment = DropdownAlignment.ToLower() == "right" ? "dropdown-menu-end" : string.Empty;
|
||||
|
||||
var languages = PageState.Languages;
|
||||
_supportedCultures = languages.Select(l => new Culture { Name = l.Code, DisplayName = l.Name });
|
||||
_supportedCultures = PageState.Languages.Select(l => new Culture { Name = l.Code, DisplayName = l.Name });
|
||||
|
||||
if (PageState.QueryString.ContainsKey("culture"))
|
||||
{
|
||||
var culture = PageState.QueryString["culture"];
|
||||
if (_supportedCultures.Any(item => item.Name == culture))
|
||||
{
|
||||
var localizationCookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture));
|
||||
HttpContext.Response.Cookies.Append(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, new CookieOptions { Path = "/", Expires = DateTimeOffset.UtcNow.AddYears(365) });
|
||||
await LocalizationCookieService.SetLocalizationCookieAsync(culture);
|
||||
}
|
||||
NavigationManager.NavigateTo(NavigationManager.Uri.Replace($"?culture={culture}", ""), forceLoad: true);
|
||||
NavigationManager.NavigateTo(NavigationManager.Uri.Replace($"?culture={culture}", ""));
|
||||
}
|
||||
}
|
||||
|
||||
@ -66,8 +60,8 @@
|
||||
{
|
||||
var localizationCookieValue = CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture));
|
||||
var interop = new Interop(JSRuntime);
|
||||
await interop.SetCookie(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, 360);
|
||||
NavigationManager.NavigateTo(NavigationManager.Uri, forceLoad: true);
|
||||
await interop.SetCookie(CookieRequestCultureProvider.DefaultCookieName, localizationCookieValue, 360, true, "Lax");
|
||||
NavigationManager.NavigateTo(NavigationManager.Uri, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,31 +4,28 @@
|
||||
@inject IStringLocalizer<SharedResources> SharedLocalizer
|
||||
|
||||
<span class="app-login">
|
||||
<AuthorizeView Roles="@RoleNames.Registered">
|
||||
<Authorizing>
|
||||
<text>...</text>
|
||||
</Authorizing>
|
||||
<Authorized>
|
||||
@if (PageState.Runtime == Runtime.Hybrid)
|
||||
{
|
||||
<button type="button" class="btn btn-primary" @onclick="LogoutUser">@Localizer["Logout"]</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<form method="post" class="app-form-inline" action="@logouturl" @formname="LogoutForm">
|
||||
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||
<input type="hidden" name="returnurl" value="@returnurl" />
|
||||
<button type="submit" class="btn btn-primary">@Localizer["Logout"]</button>
|
||||
</form>
|
||||
}
|
||||
</Authorized>
|
||||
<NotAuthorized>
|
||||
@if (ShowLogin)
|
||||
{
|
||||
<a href="@loginurl" class="btn btn-primary">@SharedLocalizer["Login"]</a>
|
||||
}
|
||||
</NotAuthorized>
|
||||
</AuthorizeView>
|
||||
@if (PageState.User != null)
|
||||
{
|
||||
@if (PageState.Runtime == Runtime.Hybrid)
|
||||
{
|
||||
<button type="button" class="btn btn-primary" @onclick="LogoutUser">@Localizer["Logout"]</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<form method="post" class="app-form-inline" action="@logouturl" @formname="LogoutForm">
|
||||
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||
<input type="hidden" name="returnurl" value="@returnurl" />
|
||||
<button type="submit" class="btn btn-primary">@Localizer["Logout"]</button>
|
||||
</form>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@if (ShowLogin)
|
||||
{
|
||||
<a href="@loginurl" class="btn btn-primary">@SharedLocalizer["Login"]</a>
|
||||
}
|
||||
}
|
||||
</span>
|
||||
|
||||
@code
|
||||
|
@ -8,4 +8,19 @@
|
||||
<img class="img-fluid" src="@Utilities.FileUrl(PageState.Alias, PageState.Site.LogoFileId.Value)" alt="@PageState.Site.Name" />
|
||||
</a>
|
||||
</span>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (UseSiteNameAsFallback)
|
||||
{
|
||||
<span class="app-logo">
|
||||
<a class="navbar-brand" href="@PageState.Alias.Path">@PageState.Site.Name</a>
|
||||
</span>
|
||||
}
|
||||
}
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public bool UseSiteNameAsFallback { get; set; } = false; // indicates if the site name should be displayed in scenarios where a site does not have a logo defined
|
||||
}
|
||||
|
||||
|
@ -1,30 +1,32 @@
|
||||
@namespace Oqtane.Themes.Controls
|
||||
@using System.Net
|
||||
@using Microsoft.AspNetCore.Http
|
||||
@inherits ThemeControlBase
|
||||
@inject ISettingService SettingService
|
||||
@inject IStringLocalizer<Search> Localizer
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
@if (_searchResultsPage != null)
|
||||
{
|
||||
<span class="app-search @CssClass">
|
||||
<span class="@_defaultCssClass @CssClass">
|
||||
<form method="post" class="app-form-inline" @formname="@($"SearchForm")" @onsubmit="@PerformSearch" data-enhance>
|
||||
<input type="hidden" name="@Constants.RequestVerificationToken" value="@SiteState.AntiForgeryToken" />
|
||||
<input type="text" name="keywords" maxlength="50"
|
||||
class="form-control d-inline-block pe-5 shadow-none"
|
||||
@bind="_keywords"
|
||||
placeholder="@Localizer["SearchPlaceHolder"]"
|
||||
aria-label="Search" />
|
||||
<button type="submit" class="btn btn-search">
|
||||
@if (AllowTextInput)
|
||||
{
|
||||
<input type="text" name="keywords" maxlength="50"
|
||||
class="form-control d-inline-block pe-5 shadow-none"
|
||||
@bind="_keywords"
|
||||
placeholder="@Localizer["SearchPlaceHolder"]"
|
||||
aria-label="Search" />
|
||||
}
|
||||
<button type="submit" class="btn btn-search" aria-label="Search Button">
|
||||
<span class="oi oi-magnifying-glass align-middle"></span>
|
||||
</button>
|
||||
</form>
|
||||
</span>
|
||||
}
|
||||
|
||||
|
||||
|
||||
@code {
|
||||
private string _defaultCssClass;
|
||||
private Page _searchResultsPage;
|
||||
private string _keywords = "";
|
||||
|
||||
@ -32,19 +34,23 @@
|
||||
public string CssClass { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string SearchResultPagePath { get; set; } = "search";
|
||||
public bool AllowTextInput { get; set; } = true; // setting to false will display only the search icon button - not the textbox
|
||||
|
||||
[CascadingParameter]
|
||||
HttpContext HttpContext { get; set; }
|
||||
[Parameter]
|
||||
public string SearchResultPagePath { get; set; } = "search"; // setting to "" will disable search
|
||||
|
||||
[SupplyParameterFromForm(FormName = "SearchForm")]
|
||||
public string KeyWords { get => ""; set => _keywords = value; }
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
if(!string.IsNullOrEmpty(SearchResultPagePath))
|
||||
if (bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "Search_Enabled", "True")))
|
||||
{
|
||||
_searchResultsPage = PageState.Pages.FirstOrDefault(i => i.Path == SearchResultPagePath);
|
||||
_defaultCssClass = (AllowTextInput) ? "app-search" : "app-search-noinput";
|
||||
if (!string.IsNullOrEmpty(SearchResultPagePath))
|
||||
{
|
||||
_searchResultsPage = PageState.Pages.FirstOrDefault(i => i.Path == SearchResultPagePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -52,7 +58,7 @@
|
||||
{
|
||||
if (_searchResultsPage != null)
|
||||
{
|
||||
var url = NavigateUrl(_searchResultsPage.Path, $"q={_keywords}");
|
||||
var url = NavigateUrl(_searchResultsPage.Path, $"q={WebUtility.UrlEncode(_keywords)}");
|
||||
NavigationManager.NavigateTo(url);
|
||||
}
|
||||
}
|
||||
|
@ -6,20 +6,17 @@
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
<span class="app-profile">
|
||||
<AuthorizeView Roles="@RoleNames.Registered">
|
||||
<Authorizing>
|
||||
<text>...</text>
|
||||
</Authorizing>
|
||||
<Authorized>
|
||||
<a href="@NavigateUrl("profile", "returnurl=" + _returnurl)" class="btn btn-primary">@context.User.Identity.Name</a>
|
||||
</Authorized>
|
||||
<NotAuthorized>
|
||||
@if (ShowRegister && PageState.Site.AllowRegistration)
|
||||
{
|
||||
<a href="@NavigateUrl("register", "returnurl=" + _returnurl)" class="btn btn-primary">@Localizer["Register"]</a>
|
||||
}
|
||||
</NotAuthorized>
|
||||
</AuthorizeView>
|
||||
@if (PageState.User != null)
|
||||
{
|
||||
<a href="@NavigateUrl("profile", "returnurl=" + _returnurl)" class="btn btn-primary">@PageState.User.Username</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
@if (ShowRegister && PageState.Site.AllowRegistration)
|
||||
{
|
||||
<a href="@NavigateUrl("register", "returnurl=" + _returnurl)" class="btn btn-primary">@Localizer["Register"]</a>
|
||||
}
|
||||
}
|
||||
</span>
|
||||
|
||||
@code {
|
||||
|
@ -21,9 +21,7 @@ namespace Oqtane.Themes.OqtaneTheme
|
||||
Integrity = "sha512-M+Wrv9LTvQe81gFD2ZE3xxPTN5V2n1iLCXsldIxXvfs6tP+6VihBCwCMBkkjkQUZVmEHBsowb9Vqsq1et1teEg==",
|
||||
CrossOrigin = "anonymous" },
|
||||
new Resource { ResourceType = ResourceType.Stylesheet, Url = "~/Theme.css" },
|
||||
new Resource { ResourceType = ResourceType.Script, Url = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.3/js/bootstrap.bundle.min.js",
|
||||
Integrity = "sha512-7Pi/otdlbbCR+LnW+F7PwFcSDJOuUJB3OxtEHbg4vSMvzvJjde4Po1v4BR9Gdc9aXNUNFVUY+SK51wWT8WF0Gg==",
|
||||
CrossOrigin = "anonymous", Location = ResourceLocation.Body },
|
||||
new Resource { ResourceType = ResourceType.Script, Url = Constants.BootstrapScriptUrl, Integrity = Constants.BootstrapScriptIntegrity, CrossOrigin = "anonymous", Location = ResourceLocation.Body }
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
|
||||
<main role="main">
|
||||
<nav class="navbar navbar-dark bg-primary fixed-top">
|
||||
<Logo /><Menu Orientation="Horizontal" />
|
||||
<Logo UseSiteNameAsFallback="true" /><Menu Orientation="Horizontal" />
|
||||
<div class="controls ms-auto">
|
||||
<div class="controls-group">
|
||||
<Search CssClass="me-3 text-center bg-primary" />
|
||||
|
@ -93,6 +93,7 @@ namespace Oqtane.Themes
|
||||
|
||||
// url methods
|
||||
|
||||
// navigate url
|
||||
public string NavigateUrl()
|
||||
{
|
||||
return NavigateUrl(PageState.Page.Path);
|
||||
@ -108,31 +109,78 @@ namespace Oqtane.Themes
|
||||
return NavigateUrl(PageState.Page.Path, refresh);
|
||||
}
|
||||
|
||||
public string NavigateUrl(string path, string querystring)
|
||||
{
|
||||
return Utilities.NavigateUrl(PageState.Alias.Path, path, querystring);
|
||||
}
|
||||
|
||||
public string NavigateUrl(string path, Dictionary<string, string> querystring)
|
||||
{
|
||||
return NavigateUrl(path, Utilities.CreateQueryString(querystring));
|
||||
}
|
||||
|
||||
public string NavigateUrl(string path, bool refresh)
|
||||
{
|
||||
return Utilities.NavigateUrl(PageState.Alias.Path, path, refresh ? "refresh" : "");
|
||||
return NavigateUrl(path, refresh ? "refresh" : "");
|
||||
}
|
||||
|
||||
public string NavigateUrl(string path, string parameters)
|
||||
public string NavigateUrl(int moduleid, string action)
|
||||
{
|
||||
return Utilities.NavigateUrl(PageState.Alias.Path, path, parameters);
|
||||
return EditUrl(moduleid, action, "");
|
||||
}
|
||||
|
||||
public string NavigateUrl(int moduleid, string action, string querystring)
|
||||
{
|
||||
return EditUrl(PageState.Page.Path, moduleid, action, querystring);
|
||||
}
|
||||
|
||||
public string NavigateUrl(int moduleid, string action, Dictionary<string, string> querystring)
|
||||
{
|
||||
return EditUrl(PageState.Page.Path, moduleid, action, Utilities.CreateQueryString(querystring));
|
||||
}
|
||||
|
||||
public string NavigateUrl(string path, int moduleId, string action)
|
||||
{
|
||||
return EditUrl(path, moduleId, action, "");
|
||||
}
|
||||
|
||||
public string NavigateUrl(string path, int moduleid, string action, string querystring)
|
||||
{
|
||||
return EditUrl(path, moduleid, action, querystring);
|
||||
}
|
||||
|
||||
public string NavigateUrl(string path, int moduleid, string action, Dictionary<string, string> querystring)
|
||||
{
|
||||
return EditUrl(path, moduleid, action, querystring);
|
||||
}
|
||||
|
||||
// edit url
|
||||
public string EditUrl(int moduleid, string action)
|
||||
{
|
||||
return EditUrl(moduleid, action, "");
|
||||
}
|
||||
|
||||
public string EditUrl(int moduleid, string action, string parameters)
|
||||
public string EditUrl(int moduleid, string action, string querystring)
|
||||
{
|
||||
return EditUrl(PageState.Page.Path, moduleid, action, parameters);
|
||||
return EditUrl(PageState.Page.Path, moduleid, action, querystring);
|
||||
}
|
||||
|
||||
public string EditUrl(string path, int moduleid, string action, string parameters)
|
||||
public string EditUrl(int moduleid, string action, Dictionary<string, string> querystring)
|
||||
{
|
||||
return Utilities.EditUrl(PageState.Alias.Path, path, moduleid, action, parameters);
|
||||
return EditUrl(PageState.Page.Path, moduleid, action, querystring);
|
||||
}
|
||||
|
||||
public string EditUrl(string path, int moduleid, string action, string querystring)
|
||||
{
|
||||
return Utilities.EditUrl(PageState.Alias.Path, path, moduleid, action, querystring);
|
||||
}
|
||||
|
||||
public string EditUrl(string path, int moduleid, string action, Dictionary<string, string> querystring)
|
||||
{
|
||||
return EditUrl(path, moduleid, action, Utilities.CreateQueryString(querystring));
|
||||
}
|
||||
|
||||
// file url
|
||||
public string FileUrl(string folderpath, string filename)
|
||||
{
|
||||
return FileUrl(folderpath, filename, false);
|
||||
@ -152,6 +200,7 @@ namespace Oqtane.Themes
|
||||
return Utilities.FileUrl(PageState.Alias, fileid, download);
|
||||
}
|
||||
|
||||
// image url
|
||||
public string ImageUrl(int fileid, int width, int height)
|
||||
{
|
||||
return ImageUrl(fileid, width, height, "");
|
||||
|
@ -16,13 +16,18 @@ namespace Oqtane.UI
|
||||
_jsRuntime = jsRuntime;
|
||||
}
|
||||
|
||||
public Task SetCookie(string name, string value, int days)
|
||||
public async Task SetCookie(string name, string value, int days)
|
||||
{
|
||||
await SetCookie(name, value, days, true, "Lax");
|
||||
}
|
||||
|
||||
public Task SetCookie(string name, string value, int days, bool secure, string sameSite)
|
||||
{
|
||||
try
|
||||
{
|
||||
_jsRuntime.InvokeVoidAsync(
|
||||
"Oqtane.Interop.setCookie",
|
||||
name, value, days);
|
||||
name, value, days, secure, sameSite);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
catch
|
||||
|
@ -1,7 +1,5 @@
|
||||
@namespace Oqtane.UI
|
||||
@using Microsoft.AspNetCore.Http
|
||||
@inject IInstallationService InstallationService
|
||||
@inject IJSRuntime JSRuntime
|
||||
@inject SiteState SiteState
|
||||
|
||||
@if (_initialized)
|
||||
@ -48,9 +46,6 @@
|
||||
[Parameter]
|
||||
public string Platform { get; set; } = "";
|
||||
|
||||
[CascadingParameter]
|
||||
HttpContext HttpContext { get; set; }
|
||||
|
||||
private bool _initialized = false;
|
||||
private bool _installed = false;
|
||||
private string _display = "display: none;";
|
||||
@ -61,9 +56,8 @@
|
||||
{
|
||||
SiteState.AntiForgeryToken = AntiForgeryToken;
|
||||
SiteState.AuthorizationToken = AuthorizationToken;
|
||||
SiteState.RemoteIPAddress = (_pageState != null) ? _pageState.RemoteIPAddress : "";
|
||||
SiteState.Platform = Platform;
|
||||
SiteState.IsPrerendering = (HttpContext != null) ? true : false;
|
||||
SiteState.IsPrerendering = !RendererInfo.IsInteractive;
|
||||
|
||||
if (Runtime == Runtimes.Hybrid)
|
||||
{
|
||||
@ -80,6 +74,7 @@
|
||||
{
|
||||
_pageState = PageState;
|
||||
SiteState.Alias = PageState.Alias;
|
||||
SiteState.RemoteIPAddress = (PageState != null) ? PageState.RemoteIPAddress : "";
|
||||
_installed = true;
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
@using System.Diagnostics.CodeAnalysis
|
||||
@using System.Net
|
||||
@using Microsoft.AspNetCore.Http
|
||||
@using System.Globalization
|
||||
@using System.Security.Claims
|
||||
@namespace Oqtane.UI
|
||||
@ -157,7 +156,7 @@
|
||||
|
||||
// verify user is authenticated for current site
|
||||
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
|
||||
if (authState.User.Identity.IsAuthenticated && authState.User.Claims.Any(item => item.Type == "sitekey" && item.Value == SiteState.Alias.SiteKey))
|
||||
if (authState.User.Identity.IsAuthenticated && authState.User.Claims.Any(item => item.Type == Constants.SiteKeyClaimType && item.Value == SiteState.Alias.SiteKey))
|
||||
{
|
||||
// get user
|
||||
var userid = int.Parse(authState.User.Claims.First(item => item.Type == ClaimTypes.NameIdentifier).Value);
|
||||
@ -261,17 +260,17 @@
|
||||
// edit mode
|
||||
if (user != null)
|
||||
{
|
||||
if (querystring.ContainsKey("editmode") && querystring["edit"] == "true")
|
||||
var editpageid = user.Settings.ContainsKey("CP-editmode") ? int.Parse(user.Settings["CP-editmode"], CultureInfo.InvariantCulture) : -1;
|
||||
if ((querystring.ContainsKey("edit") && querystring["edit"] == "true") || page.PageId == editpageid)
|
||||
{
|
||||
editmode = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
editmode = (page.PageId == ((user.Settings.ContainsKey("CP-editmode")) ? int.Parse(user.Settings["CP-editmode"], CultureInfo.InvariantCulture) : -1));
|
||||
if (!editmode)
|
||||
if (editpageid != -1)
|
||||
{
|
||||
var userSettings = new Dictionary<string, string> { { "CP-editmode", "-1" } };
|
||||
await SettingService.UpdateUserSettingsAsync(userSettings, user.UserId);
|
||||
// reset edit mode page
|
||||
await SettingService.AddOrUpdateSettingAsync(EntityNames.User, user.UserId, "CP-editmode", "-1", false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -287,10 +286,10 @@
|
||||
}
|
||||
|
||||
// load additional metadata for current page
|
||||
page = ProcessPage(page, site, user, SiteState.Alias);
|
||||
page = ProcessPage(page, site, user, SiteState.Alias, action);
|
||||
|
||||
// load additional metadata for modules
|
||||
(page, modules) = ProcessModules(page, modules, moduleid, action, (!string.IsNullOrEmpty(page.DefaultContainerType)) ? page.DefaultContainerType : site.DefaultContainerType, SiteState.Alias);
|
||||
(page, modules) = ProcessModules(site, page, modules, moduleid, action, (!string.IsNullOrEmpty(page.DefaultContainerType)) ? page.DefaultContainerType : site.DefaultContainerType, SiteState.Alias);
|
||||
|
||||
// populate page state (which acts as a client-side cache for subsequent requests)
|
||||
_pagestate = new PageState
|
||||
@ -366,7 +365,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
private Page ProcessPage(Page page, Site site, User user, Alias alias)
|
||||
private Page ProcessPage(Page page, Site site, User user, Alias alias, string action)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -403,6 +402,16 @@
|
||||
page.Resources = ManagePageResources(page.Resources, themeobject.Resources, ResourceLevel.Page, alias, "Themes", themetype.Namespace);
|
||||
}
|
||||
}
|
||||
// theme settings components are dynamically loaded within the framework Page Management module
|
||||
if (page.Path == "admin/pages" && action.ToLower() == "edit" && theme != null && !string.IsNullOrEmpty(theme.ThemeSettingsType))
|
||||
{
|
||||
var settingsType = Type.GetType(theme.ThemeSettingsType);
|
||||
if (settingsType != null)
|
||||
{
|
||||
var objSettings = Activator.CreateInstance(settingsType) as IModuleControl;
|
||||
page.Resources = ManagePageResources(page.Resources, objSettings.Resources, ResourceLevel.Module, alias, "Modules", settingsType.Namespace);
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(panes))
|
||||
{
|
||||
@ -426,7 +435,7 @@
|
||||
return page;
|
||||
}
|
||||
|
||||
private (Page Page, List<Module> Modules) ProcessModules(Page page, List<Module> modules, int moduleid, string action, string defaultcontainertype, Alias alias)
|
||||
private (Page Page, List<Module> Modules) ProcessModules(Site site, Page page, List<Module> modules, int moduleid, string action, string defaultcontainertype, Alias alias)
|
||||
{
|
||||
var paneindex = new Dictionary<string, int>();
|
||||
|
||||
@ -494,15 +503,40 @@
|
||||
module.Prerender = moduleobject.Prerender;
|
||||
|
||||
page.Resources = ManagePageResources(page.Resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace);
|
||||
|
||||
// settings components are dynamically loaded within the framework Settings module
|
||||
if (action.ToLower() == "settings" && module.ModuleDefinition != null)
|
||||
{
|
||||
// settings components are embedded within a framework settings module
|
||||
moduletype = Type.GetType(module.ModuleDefinition.ControlTypeTemplate.Replace(Constants.ActionToken, action), false, true);
|
||||
// module settings component
|
||||
var settingsType = "";
|
||||
if (!string.IsNullOrEmpty(module.ModuleDefinition.SettingsType))
|
||||
{
|
||||
// module settings type explicitly declared in IModule interface
|
||||
settingsType = module.ModuleDefinition.SettingsType;
|
||||
}
|
||||
else
|
||||
{
|
||||
// legacy support - module settings type determined by convention
|
||||
settingsType = module.ModuleDefinition.ControlTypeTemplate.Replace(Constants.ActionToken, action);
|
||||
}
|
||||
moduletype = Type.GetType(settingsType, false, true);
|
||||
if (moduletype != null)
|
||||
{
|
||||
moduleobject = Activator.CreateInstance(moduletype) as IModuleControl;
|
||||
page.Resources = ManagePageResources(page.Resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace);
|
||||
}
|
||||
|
||||
// container settings component
|
||||
var theme = site.Themes.FirstOrDefault(item => item.Themes.Any(item => item.TypeName == page.ThemeType));
|
||||
if (theme != null && !string.IsNullOrEmpty(theme.ContainerSettingsType))
|
||||
{
|
||||
moduletype = Type.GetType(theme.ContainerSettingsType);
|
||||
if (moduletype != null)
|
||||
{
|
||||
moduleobject = Activator.CreateInstance(moduletype) as IModuleControl;
|
||||
page.Resources = ManagePageResources(page.Resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// additional metadata needed for admin components
|
||||
|
@ -20,6 +20,13 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// force authenticated user to provide email address (email may be missing if using external login)
|
||||
if (PageState.User != null && PageState.User.IsAuthenticated && string.IsNullOrEmpty(PageState.User.Email) && PageState.Route.PagePath != "profile")
|
||||
{
|
||||
NavigationManager.NavigateTo(Utilities.NavigateUrl(PageState.Alias.Path, "profile", "returnurl=" + WebUtility.UrlEncode(PageState.Route.PathAndQuery)));
|
||||
return;
|
||||
}
|
||||
|
||||
// set page title
|
||||
if (!string.IsNullOrEmpty(PageState.Page.Title))
|
||||
{
|
||||
@ -44,7 +51,6 @@
|
||||
}
|
||||
|
||||
// head content
|
||||
AddHeadContent(headcontent, PageState.Site.HeadContent);
|
||||
if (!string.IsNullOrEmpty(PageState.Site.HeadContent))
|
||||
{
|
||||
headcontent = AddHeadContent(headcontent, PageState.Site.HeadContent);
|
||||
@ -66,30 +72,24 @@
|
||||
{
|
||||
if (!string.IsNullOrEmpty(content))
|
||||
{
|
||||
if (PageState.RenderMode == RenderModes.Interactive)
|
||||
var elements = content.Split('<', StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var element in elements)
|
||||
{
|
||||
// remove scripts
|
||||
var index = content.IndexOf("<script");
|
||||
while (index >= 0)
|
||||
if (PageState.RenderMode == RenderModes.Static || (!element.ToLower().StartsWith("script") && !element.ToLower().StartsWith("/script")))
|
||||
{
|
||||
content = content.Remove(index, content.IndexOf("</script>") + 9 - index);
|
||||
index = content.IndexOf("<script");
|
||||
if (!headcontent.Contains("<" + element) || element.StartsWith("/"))
|
||||
{
|
||||
headcontent += "<" + element;
|
||||
}
|
||||
}
|
||||
}
|
||||
headcontent += content + "\n";
|
||||
headcontent += "\n";
|
||||
}
|
||||
return headcontent;
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
// force authenticated user to provide email address (email may be missing if using external login)
|
||||
if (PageState.User != null && PageState.User.IsAuthenticated && string.IsNullOrEmpty(PageState.User.Email) && PageState.Route.PagePath != "profile")
|
||||
{
|
||||
NavigationManager.NavigateTo(Utilities.NavigateUrl(PageState.Alias.Path, "profile", "returnurl=" + WebUtility.UrlEncode(PageState.Route.PathAndQuery)));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!firstRender)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(PageState.Page.HeadContent) && PageState.Page.HeadContent.Contains("<script"))
|
||||
|
@ -1,8 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Version>5.2.0</Version>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Version>6.0.0</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/v5.2.0</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.0</PackageReleaseNotes>
|
||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
@ -33,8 +33,8 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MySql.EntityFrameworkCore" Version="8.0.5" />
|
||||
<PackageReference Include="MySql.Data" Version="9.0.0" />
|
||||
<PackageReference Include="MySql.EntityFrameworkCore" Version="9.0.0-preview" />
|
||||
<PackageReference Include="MySql.Data" Version="9.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -42,7 +42,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<MySQLFiles Include="$(OutputPath)Oqtane.Database.MySQL.dll;$(OutputPath)Oqtane.Database.MySQL.pdb;$(OutputPath)MySql.EntityFrameworkCore.dll;$(OutputPath)MySql.Data.dll" DestinationPath="..\Oqtane.Server\bin\$(Configuration)\net8.0\%(Filename)%(Extension)" />
|
||||
<MySQLFiles Include="$(OutputPath)Oqtane.Database.MySQL.dll;$(OutputPath)Oqtane.Database.MySQL.pdb;$(OutputPath)MySql.EntityFrameworkCore.dll;$(OutputPath)MySql.Data.dll" DestinationPath="..\Oqtane.Server\bin\$(Configuration)\net9.0\%(Filename)%(Extension)" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PublishProvider" AfterTargets="PostBuildEvent" Inputs="@(MySQLFiles)" Outputs="@(MySQLFiles->'%(DestinationPath)')">
|
||||
|
@ -1,8 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Version>5.2.0</Version>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Version>6.0.0</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/v5.2.0</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.0</PackageReleaseNotes>
|
||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
@ -25,17 +25,18 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||
<NoWarn>1701;1702;EF1001;AD0001</NoWarn>
|
||||
<NoWarn>1701;1702;EF1001;AD0001;NU1608</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||
<NoWarn>1701;1702;EF1001;AD0001</NoWarn>
|
||||
<NoWarn>1701;1702;EF1001;AD0001;NU1608</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="EFCore.NamingConventions" Version="8.0.3" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.7" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.0" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.0-rc.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -43,7 +44,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PostgreSQLFiles Include="$(OutputPath)Oqtane.Database.PostgreSQL.dll;$(OutputPath)Oqtane.Database.PostgreSQL.pdb;$(OutputPath)EFCore.NamingConventions.dll;$(OutputPath)Npgsql.EntityFrameworkCore.PostgreSQL.dll;$(OutputPath)Npgsql.dll" DestinationPath="..\Oqtane.Server\bin\$(Configuration)\net8.0\%(Filename)%(Extension)" />
|
||||
<PostgreSQLFiles Include="$(OutputPath)Oqtane.Database.PostgreSQL.dll;$(OutputPath)Oqtane.Database.PostgreSQL.pdb;$(OutputPath)EFCore.NamingConventions.dll;$(OutputPath)Npgsql.EntityFrameworkCore.PostgreSQL.dll;$(OutputPath)Npgsql.dll" DestinationPath="..\Oqtane.Server\bin\$(Configuration)\net9.0\%(Filename)%(Extension)" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PublishProvider" AfterTargets="PostBuildEvent" Inputs="@(PostgreSQLFiles)" Outputs="@(PostgreSQLFiles->'%(DestinationPath)')">
|
||||
|
@ -1,8 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Version>5.2.0</Version>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Version>6.0.0</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/v5.2.0</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.0</PackageReleaseNotes>
|
||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
@ -33,7 +33,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.7" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -41,7 +41,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<SqlServerFiles Include="$(OutputPath)Oqtane.Database.SqlServer.dll;$(OutputPath)Oqtane.Database.SqlServer.pdb;$(OutputPath)Microsoft.EntityFrameworkCore.SqlServer.dll" DestinationPath="..\Oqtane.Server\bin\$(Configuration)\net8.0\%(Filename)%(Extension)" />
|
||||
<SqlServerFiles Include="$(OutputPath)Oqtane.Database.SqlServer.dll;$(OutputPath)Oqtane.Database.SqlServer.pdb;$(OutputPath)Microsoft.EntityFrameworkCore.SqlServer.dll" DestinationPath="..\Oqtane.Server\bin\$(Configuration)\net9.0\%(Filename)%(Extension)" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PublishProvider" AfterTargets="PostBuildEvent" Inputs="@(SqlServerFiles)" Outputs="@(SqlServerFiles->'%(DestinationPath)')">
|
||||
|
@ -1,8 +1,8 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Version>5.2.0</Version>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Version>6.0.0</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/v5.2.0</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.0</PackageReleaseNotes>
|
||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
@ -33,7 +33,7 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.7" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -41,7 +41,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<SqliteFiles Include="$(OutputPath)Oqtane.Database.Sqlite.dll;$(OutputPath)Oqtane.Database.Sqlite.pdb;$(OutputPath)Microsoft.EntityFrameworkCore.Sqlite.dll" DestinationPath="..\Oqtane.Server\bin\$(Configuration)\net8.0\%(Filename)%(Extension)" />
|
||||
<SqliteFiles Include="$(OutputPath)Oqtane.Database.Sqlite.dll;$(OutputPath)Oqtane.Database.Sqlite.pdb;$(OutputPath)Microsoft.EntityFrameworkCore.Sqlite.dll" DestinationPath="..\Oqtane.Server\bin\$(Configuration)\net9.0\%(Filename)%(Extension)" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PublishProvider" AfterTargets="PostBuildEvent" Inputs="@(SqliteFiles)" Outputs="@(SqliteFiles->'%(DestinationPath)')">
|
||||
|
@ -5,7 +5,10 @@ public partial class App : Application
|
||||
public App()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
MainPage = new MainPage();
|
||||
}
|
||||
|
||||
protected override Window CreateWindow(IActivationState activationState)
|
||||
{
|
||||
return new Window(new MainPage());
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net8.0-windows10.0.19041.0</TargetFrameworks>
|
||||
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net9.0-windows10.0.19041.0</TargetFrameworks>
|
||||
<!-- Uncomment to also build the tizen app. You will need to install tizen by following this: https://github.com/Samsung/Tizen.NET -->
|
||||
<!-- <TargetFrameworks>net8.0-android;net8.0-ios;net8.0-maccatalyst</TargetFrameworks> -->
|
||||
<!-- <TargetFrameworks>$(TargetFrameworks);net8.0-tizen</TargetFrameworks> -->
|
||||
<!-- <TargetFrameworks>net9.0-android;net9.0-ios;net9.0-maccatalyst</TargetFrameworks> -->
|
||||
<!-- <TargetFrameworks>$(TargetFrameworks);net9.0-tizen</TargetFrameworks> -->
|
||||
<OutputType>Exe</OutputType>
|
||||
<Version>5.2.0</Version>
|
||||
<Version>6.0.0</Version>
|
||||
<Product>Oqtane</Product>
|
||||
<Authors>Shaun Walker</Authors>
|
||||
<Company>.NET Foundation</Company>
|
||||
@ -14,7 +14,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/v5.2.0</PackageReleaseNotes>
|
||||
<PackageReleaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.0</PackageReleaseNotes>
|
||||
<RepositoryUrl>https://github.com/oqtane/oqtane.framework</RepositoryUrl>
|
||||
<RepositoryType>Git</RepositoryType>
|
||||
<RootNamespace>Oqtane.Maui</RootNamespace>
|
||||
@ -28,19 +28,21 @@
|
||||
|
||||
<!-- App Identifier -->
|
||||
<ApplicationId>com.oqtane.maui</ApplicationId>
|
||||
<ApplicationIdGuid>0E29FC31-1B83-48ED-B6E0-9F3C67B775D4</ApplicationIdGuid>
|
||||
|
||||
<!-- Versions -->
|
||||
<ApplicationDisplayVersion>5.2.0</ApplicationDisplayVersion>
|
||||
<ApplicationDisplayVersion>6.0.0</ApplicationDisplayVersion>
|
||||
<ApplicationVersion>1</ApplicationVersion>
|
||||
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">14.2</SupportedOSPlatformVersion>
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">14.0</SupportedOSPlatformVersion>
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">24.0</SupportedOSPlatformVersion>
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</SupportedOSPlatformVersion>
|
||||
<TargetPlatformMinVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</TargetPlatformMinVersion>
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'tizen'">6.5</SupportedOSPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<!-- To develop, package, and publish an app to the Microsoft Store, see: https://aka.ms/MauiTemplateUnpackaged -->
|
||||
<WindowsPackageType>None</WindowsPackageType>
|
||||
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">15.0</SupportedOSPlatformVersion>
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">15.0</SupportedOSPlatformVersion>
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">24.0</SupportedOSPlatformVersion>
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</SupportedOSPlatformVersion>
|
||||
<TargetPlatformMinVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</TargetPlatformMinVersion>
|
||||
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'tizen'">6.5</SupportedOSPlatformVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- App Icon -->
|
||||
@ -65,23 +67,22 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="8.0.6" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.6" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Localization" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="8.0.6" />
|
||||
<PackageReference Include="System.Net.Http.Json" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Maui.Controls" Version="8.0.40" />
|
||||
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="8.0.40" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebView.Maui" Version="8.0.40" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Localization" Version="9.0.0" />
|
||||
<PackageReference Include="System.Net.Http.Json" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Maui.Controls" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="9.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebView.Maui" Version="9.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Oqtane.Client">
|
||||
<HintPath>..\Oqtane.Server\bin\Debug\net8.0\Oqtane.Client.dll</HintPath>
|
||||
<HintPath>..\Oqtane.Server\bin\Debug\net9.0\Oqtane.Client.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Oqtane.Shared">
|
||||
<HintPath>..\Oqtane.Server\bin\Debug\net8.0\Oqtane.Shared.dll</HintPath>
|
||||
<HintPath>..\Oqtane.Server\bin\Debug\net9.0\Oqtane.Shared.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"profiles": {
|
||||
"Windows Machine": {
|
||||
"commandName": "MsixPackage",
|
||||
"commandName": "Project",
|
||||
"nativeDebugging": false
|
||||
}
|
||||
}
|
||||
|
@ -35,6 +35,9 @@ app {
|
||||
}
|
||||
|
||||
/* Action Dialog */
|
||||
.app-actiondialog{
|
||||
position: absolute;
|
||||
}
|
||||
.app-actiondialog .modal {
|
||||
position: fixed; /* Stay in place */
|
||||
z-index: 9999; /* Sit on top */
|
||||
@ -230,5 +233,41 @@ app {
|
||||
}
|
||||
|
||||
.app-form-inline {
|
||||
display: inline-block;
|
||||
display: inline;
|
||||
}
|
||||
.app-search{
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
}
|
||||
.app-search input + button{
|
||||
background: none;
|
||||
border: none;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
.app-search input + button .oi{
|
||||
top: 0;
|
||||
}
|
||||
.app-search-noinput {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
}
|
||||
.app-search-noinput button {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--bs-heading-color);
|
||||
}
|
||||
.app-search-noinput button:hover {
|
||||
color: var(--bs-heading-color);
|
||||
}
|
||||
|
||||
/* Text Editor */
|
||||
.text-area-editor > textarea {
|
||||
width: 100%;
|
||||
min-height: 250px;
|
||||
}
|
||||
|
||||
.app-logo .navbar-brand {
|
||||
padding: 5px 20px 5px 20px;
|
||||
}
|
@ -1,11 +1,18 @@
|
||||
var Oqtane = Oqtane || {};
|
||||
|
||||
Oqtane.Interop = {
|
||||
setCookie: function (name, value, days) {
|
||||
setCookie: function (name, value, days, secure, sameSite) {
|
||||
var d = new Date();
|
||||
d.setTime(d.getTime() + (days * 24 * 60 * 60 * 1000));
|
||||
var expires = "expires=" + d.toUTCString();
|
||||
document.cookie = name + "=" + value + ";" + expires + ";path=/";
|
||||
var cookieString = name + "=" + value + ";" + expires + ";path=/";
|
||||
if (secure) {
|
||||
cookieString += "; secure";
|
||||
}
|
||||
if (sameSite === "Lax" || sameSite === "Strict" || sameSite === "None") {
|
||||
cookieString += "; SameSite=" + sameSite;
|
||||
}
|
||||
document.cookie = cookieString;
|
||||
},
|
||||
getCookie: function (name) {
|
||||
name = name + "=";
|
||||
@ -198,7 +205,9 @@ Oqtane.Interop = {
|
||||
}
|
||||
promises.push(new Promise((resolve, reject) => {
|
||||
if (loadjs.isDefined(bundles[b])) {
|
||||
resolve(true);
|
||||
loadjs.ready(bundles[b], () => {
|
||||
resolve(true);
|
||||
});
|
||||
}
|
||||
else {
|
||||
loadjs(urls, bundles[b], {
|
||||
@ -206,18 +215,25 @@ Oqtane.Interop = {
|
||||
returnPromise: true,
|
||||
before: function (path, element) {
|
||||
for (let s = 0; s < scripts.length; s++) {
|
||||
if (path === scripts[s].href && scripts[s].integrity !== '') {
|
||||
element.integrity = scripts[s].integrity;
|
||||
}
|
||||
if (path === scripts[s].href && scripts[s].crossorigin !== '') {
|
||||
element.crossOrigin = scripts[s].crossorigin;
|
||||
}
|
||||
if (path === scripts[s].href && scripts[s].es6module === true) {
|
||||
element.type = "module";
|
||||
}
|
||||
if (path === scripts[s].href && scripts[s].location === 'body') {
|
||||
document.body.appendChild(element);
|
||||
return false; // return false to bypass default DOM insertion mechanism
|
||||
if (path === scripts[s].href) {
|
||||
if (scripts[s].integrity !== '') {
|
||||
element.integrity = scripts[s].integrity;
|
||||
}
|
||||
if (scripts[s].crossorigin !== '') {
|
||||
element.crossOrigin = scripts[s].crossorigin;
|
||||
}
|
||||
if (scripts[s].es6module === true) {
|
||||
element.type = "module";
|
||||
}
|
||||
if (typeof scripts[s].dataAttributes !== "undefined" && scripts[s].dataAttributes !== null) {
|
||||
for (var key in scripts[s].dataAttributes) {
|
||||
element.setAttribute(key, scripts[s].dataAttributes[key]);
|
||||
}
|
||||
}
|
||||
if (scripts[s].location === 'body') {
|
||||
document.body.appendChild(element);
|
||||
return false; // return false to bypass default DOM insertion mechanism
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -286,41 +302,49 @@ Oqtane.Interop = {
|
||||
},
|
||||
uploadFiles: function (posturl, folder, id, antiforgerytoken, jwt) {
|
||||
var fileinput = document.getElementById('FileInput_' + id);
|
||||
var files = fileinput.files;
|
||||
var progressinfo = document.getElementById('ProgressInfo_' + id);
|
||||
var progressbar = document.getElementById('ProgressBar_' + id);
|
||||
|
||||
if (progressinfo !== null && progressbar !== null) {
|
||||
progressinfo.setAttribute("style", "display: inline;");
|
||||
progressinfo.innerHTML = '';
|
||||
progressbar.setAttribute("style", "width: 100%; display: inline;");
|
||||
progressbar.value = 0;
|
||||
}
|
||||
|
||||
var files = fileinput.files;
|
||||
var totalSize = 0;
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
var FileChunk = [];
|
||||
var file = files[i];
|
||||
var MaxFileSizeMB = 1;
|
||||
var BufferChunkSize = MaxFileSizeMB * (1024 * 1024);
|
||||
var FileStreamPos = 0;
|
||||
var EndPos = BufferChunkSize;
|
||||
var Size = file.size;
|
||||
totalSize = totalSize + files[i].size;
|
||||
}
|
||||
|
||||
while (FileStreamPos < Size) {
|
||||
FileChunk.push(file.slice(FileStreamPos, EndPos));
|
||||
FileStreamPos = EndPos;
|
||||
EndPos = FileStreamPos + BufferChunkSize;
|
||||
var maxChunkSizeMB = 1;
|
||||
var bufferChunkSize = maxChunkSizeMB * (1024 * 1024);
|
||||
var uploadedSize = 0;
|
||||
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
var fileChunk = [];
|
||||
var file = files[i];
|
||||
var fileStreamPos = 0;
|
||||
var endPos = bufferChunkSize;
|
||||
|
||||
while (fileStreamPos < file.size) {
|
||||
fileChunk.push(file.slice(fileStreamPos, endPos));
|
||||
fileStreamPos = endPos;
|
||||
endPos = fileStreamPos + bufferChunkSize;
|
||||
}
|
||||
|
||||
var TotalParts = FileChunk.length;
|
||||
var PartCount = 0;
|
||||
var totalParts = fileChunk.length;
|
||||
var partCount = 0;
|
||||
|
||||
while (Chunk = FileChunk.shift()) {
|
||||
PartCount++;
|
||||
var FileName = file.name + ".part_" + PartCount.toString().padStart(3, '0') + "_" + TotalParts.toString().padStart(3, '0');
|
||||
while (chunk = fileChunk.shift()) {
|
||||
partCount++;
|
||||
var fileName = file.name + ".part_" + partCount.toString().padStart(3, '0') + "_" + totalParts.toString().padStart(3, '0');
|
||||
|
||||
var data = new FormData();
|
||||
data.append('__RequestVerificationToken', antiforgerytoken);
|
||||
data.append('folder', folder);
|
||||
data.append('formfile', Chunk, FileName);
|
||||
data.append('formfile', chunk, fileName);
|
||||
var request = new XMLHttpRequest();
|
||||
request.open('POST', posturl, true);
|
||||
if (jwt !== "") {
|
||||
@ -328,28 +352,36 @@ Oqtane.Interop = {
|
||||
request.withCredentials = true;
|
||||
}
|
||||
request.upload.onloadstart = function (e) {
|
||||
if (progressinfo !== null && progressbar !== null) {
|
||||
progressinfo.innerHTML = file.name + ' 0%';
|
||||
progressbar.value = 0;
|
||||
if (progressinfo !== null && progressbar !== null && progressinfo.innerHTML === '') {
|
||||
if (files.length === 1) {
|
||||
progressinfo.innerHTML = file.name;
|
||||
}
|
||||
else {
|
||||
progressinfo.innerHTML = file.name + ", ...";
|
||||
}
|
||||
}
|
||||
};
|
||||
request.upload.onprogress = function (e) {
|
||||
if (progressinfo !== null && progressbar !== null) {
|
||||
var percent = Math.ceil((e.loaded / e.total) * 100);
|
||||
progressinfo.innerHTML = file.name + '[' + PartCount + '] ' + percent + '%';
|
||||
var percent = Math.ceil(((uploadedSize + e.loaded) / totalSize) * 100);
|
||||
progressbar.value = (percent / 100);
|
||||
}
|
||||
};
|
||||
request.upload.onloadend = function (e) {
|
||||
if (progressinfo !== null && progressbar !== null) {
|
||||
progressinfo.innerHTML = file.name + ' 100%';
|
||||
progressbar.value = 1;
|
||||
uploadedSize = uploadedSize + e.total;
|
||||
var percent = Math.ceil((uploadedSize / totalSize) * 100);
|
||||
progressbar.value = (percent / 100);
|
||||
}
|
||||
};
|
||||
request.upload.onerror = function() {
|
||||
if (progressinfo !== null && progressbar !== null) {
|
||||
progressinfo.innerHTML = file.name + ' Error: ' + request.statusText;
|
||||
progressbar.value = 0;
|
||||
if (files.length === 1) {
|
||||
progressinfo.innerHTML = file.name + ' Error: ' + request.statusText;
|
||||
}
|
||||
else {
|
||||
progressinfo.innerHTML = ' Error: ' + request.statusText;
|
||||
}
|
||||
}
|
||||
};
|
||||
request.send(data);
|
||||
@ -385,11 +417,20 @@ Oqtane.Interop = {
|
||||
}
|
||||
},
|
||||
scrollTo: function (top, left, behavior) {
|
||||
window.scrollTo({
|
||||
top: top,
|
||||
left: left,
|
||||
behavior: behavior
|
||||
});
|
||||
const modal = document.querySelector('.modal');
|
||||
if (modal) {
|
||||
modal.scrollTo({
|
||||
top: top,
|
||||
left: left,
|
||||
behavior: behavior
|
||||
});
|
||||
} else {
|
||||
window.scrollTo({
|
||||
top: top,
|
||||
left: left,
|
||||
behavior: behavior
|
||||
});
|
||||
}
|
||||
},
|
||||
scrollToId: function (id) {
|
||||
var element = document.getElementById(id);
|
||||
|
@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||
<package>
|
||||
<metadata>
|
||||
<id>Oqtane.Client</id>
|
||||
<version>5.2.0</version>
|
||||
<version>6.0.0</version>
|
||||
<authors>Shaun Walker</authors>
|
||||
<owners>.NET Foundation</owners>
|
||||
<title>Oqtane Framework</title>
|
||||
@ -12,13 +12,15 @@
|
||||
<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/v5.2.0</releaseNotes>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.0</releaseNotes>
|
||||
<readme>readme.md</readme>
|
||||
<icon>icon.png</icon>
|
||||
<tags>oqtane</tags>
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="..\Oqtane.Client\bin\Release\net8.0\Oqtane.Client.dll" target="lib\net8.0" />
|
||||
<file src="..\Oqtane.Client\bin\Release\net8.0\Oqtane.Client.pdb" target="lib\net8.0" />
|
||||
<file src="..\Oqtane.Client\bin\Release\net8.0\Oqtane.Client.dll" target="lib\net9.0" />
|
||||
<file src="..\Oqtane.Client\bin\Release\net8.0\Oqtane.Client.pdb" target="lib\net9.0" />
|
||||
<file src="icon.png" target="" />
|
||||
<file src="readme.md" target="" />
|
||||
</files>
|
||||
</package>
|
||||
|
@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||
<package>
|
||||
<metadata>
|
||||
<id>Oqtane.Framework</id>
|
||||
<version>5.2.0</version>
|
||||
<version>6.0.0</version>
|
||||
<authors>Shaun Walker</authors>
|
||||
<owners>.NET Foundation</owners>
|
||||
<title>Oqtane Framework</title>
|
||||
@ -11,12 +11,14 @@
|
||||
<copyright>.NET Foundation</copyright>
|
||||
<requireLicenseAcceptance>false</requireLicenseAcceptance>
|
||||
<license type="expression">MIT</license>
|
||||
<projectUrl>https://github.com/oqtane/oqtane.framework/releases/download/v5.2.0/Oqtane.Framework.5.2.0.Upgrade.zip</projectUrl>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v5.2.0</releaseNotes>
|
||||
<projectUrl>https://github.com/oqtane/oqtane.framework/releases/download/v6.0.0/Oqtane.Framework.6.0.0.Upgrade.zip</projectUrl>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.0</releaseNotes>
|
||||
<readme>readme.md</readme>
|
||||
<icon>icon.png</icon>
|
||||
<tags>oqtane framework</tags>
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="icon.png" target="" />
|
||||
<file src="readme.md" target="" />
|
||||
</files>
|
||||
</package>
|
||||
|
@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||
<package>
|
||||
<metadata>
|
||||
<id>Oqtane.Server</id>
|
||||
<version>5.2.0</version>
|
||||
<version>6.0.0</version>
|
||||
<authors>Shaun Walker</authors>
|
||||
<owners>.NET Foundation</owners>
|
||||
<title>Oqtane Framework</title>
|
||||
@ -12,13 +12,15 @@
|
||||
<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/v5.2.0</releaseNotes>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.0</releaseNotes>
|
||||
<readme>readme.md</readme>
|
||||
<icon>icon.png</icon>
|
||||
<tags>oqtane</tags>
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="..\Oqtane.Server\bin\Release\net8.0\Oqtane.Server.dll" target="lib\net8.0" />
|
||||
<file src="..\Oqtane.Server\bin\Release\net8.0\Oqtane.Server.pdb" target="lib\net8.0" />
|
||||
<file src="..\Oqtane.Server\bin\Release\net8.0\Oqtane.Server.dll" target="lib\net9.0" />
|
||||
<file src="..\Oqtane.Server\bin\Release\net8.0\Oqtane.Server.pdb" target="lib\net9.0" />
|
||||
<file src="icon.png" target="" />
|
||||
<file src="readme.md" target="" />
|
||||
</files>
|
||||
</package>
|
||||
|
@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||
<package>
|
||||
<metadata>
|
||||
<id>Oqtane.Shared</id>
|
||||
<version>5.2.0</version>
|
||||
<version>6.0.0</version>
|
||||
<authors>Shaun Walker</authors>
|
||||
<owners>.NET Foundation</owners>
|
||||
<title>Oqtane Framework</title>
|
||||
@ -12,13 +12,15 @@
|
||||
<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/v5.2.0</releaseNotes>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.0</releaseNotes>
|
||||
<readme>readme.md</readme>
|
||||
<icon>icon.png</icon>
|
||||
<tags>oqtane</tags>
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="..\Oqtane.Shared\bin\Release\net8.0\Oqtane.Shared.dll" target="lib\net8.0" />
|
||||
<file src="..\Oqtane.Shared\bin\Release\net8.0\Oqtane.Shared.pdb" target="lib\net8.0" />
|
||||
<file src="..\Oqtane.Shared\bin\Release\net8.0\Oqtane.Shared.dll" target="lib\net9.0" />
|
||||
<file src="..\Oqtane.Shared\bin\Release\net8.0\Oqtane.Shared.pdb" target="lib\net9.0" />
|
||||
<file src="icon.png" target="" />
|
||||
<file src="readme.md" target="" />
|
||||
</files>
|
||||
</package>
|
||||
|
@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||
<package>
|
||||
<metadata>
|
||||
<id>Oqtane.Updater</id>
|
||||
<version>5.2.0</version>
|
||||
<version>6.0.0</version>
|
||||
<authors>Shaun Walker</authors>
|
||||
<owners>.NET Foundation</owners>
|
||||
<title>Oqtane Framework</title>
|
||||
@ -12,12 +12,14 @@
|
||||
<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/v5.2.0</releaseNotes>
|
||||
<releaseNotes>https://github.com/oqtane/oqtane.framework/releases/tag/v6.0.0</releaseNotes>
|
||||
<readme>readme.md</readme>
|
||||
<icon>icon.png</icon>
|
||||
<tags>oqtane</tags>
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="..\Oqtane.Updater\bin\Release\net8.0\publish\*.*" target="lib\net8.0" />
|
||||
<file src="..\Oqtane.Updater\bin\Release\net8.0\publish\*.*" target="lib\net9.0" />
|
||||
<file src="icon.png" target="" />
|
||||
<file src="readme.md" target="" />
|
||||
</files>
|
||||
</package>
|
||||
|
@ -1 +1 @@
|
||||
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net8.0\publish\*" -DestinationPath "Oqtane.Framework.5.2.0.Install.zip" -Force
|
||||
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net9.0\publish\*" -DestinationPath "Oqtane.Framework.6.0.0.Install.zip" -Force
|
||||
|
Binary file not shown.
9
Oqtane.Package/readme.md
Normal file
9
Oqtane.Package/readme.md
Normal file
@ -0,0 +1,9 @@
|
||||
# Oqtane Framework
|
||||
|
||||

|
||||
|
||||
Oqtane is an open source CMS and Application Framework that provides advanced functionality for developing web, mobile, and desktop applications on .NET. It leverages Blazor to compose a fully dynamic digital experience which can be hosted on Static Blazor, Blazor Server, Blazor WebAssembly, or Blazor Hybrid (via .NET MAUI).
|
||||
|
||||
Oqtane was created by [Shaun Walker](https://www.linkedin.com/in/shaunbrucewalker/) and was inspired by his earlier efforts with DotNetNuke... however Oqtane is a native Blazor application written from the ground up using modern .NET Core technology and a Single Page Application (SPA) architecture. It is a modular application framework offering a fully dynamic page compositing model, multi-site support, designer friendly themes, and extensibility via third party modules.
|
||||
|
||||
More information about Oqtane can be found at: [https://www.oqtane.org](https://www.oqtane.org)
|
@ -6,14 +6,22 @@ nuget.exe pack Oqtane.Client.nuspec
|
||||
nuget.exe pack Oqtane.Server.nuspec
|
||||
nuget.exe pack Oqtane.Shared.nuspec
|
||||
nuget.exe pack Oqtane.Framework.nuspec
|
||||
del /F/Q/S "..\Oqtane.Server\bin\Release\net8.0\publish" > NUL
|
||||
rmdir /Q/S "..\Oqtane.Server\bin\Release\net8.0\publish"
|
||||
del /F/Q/S "..\Oqtane.Server\bin\Release\net9.0\publish" > NUL
|
||||
rmdir /Q/S "..\Oqtane.Server\bin\Release\net9.0\publish"
|
||||
dotnet publish ..\Oqtane.Server\Oqtane.Server.csproj /p:Configuration=Release
|
||||
del /F/Q/S "..\Oqtane.Server\bin\Release\net8.0\publish\wwwroot\Content" > NUL
|
||||
rmdir /Q/S "..\Oqtane.Server\bin\Release\net8.0\publish\wwwroot\Content"
|
||||
del /F/Q/S "..\Oqtane.Server\bin\Release\net9.0\publish\wwwroot\Content" > NUL
|
||||
rmdir /Q/S "..\Oqtane.Server\bin\Release\net9.0\publish\wwwroot\Content"
|
||||
setlocal ENABLEDELAYEDEXPANSION
|
||||
set retain=Placeholder.txt
|
||||
for /D %%i in ("..\Oqtane.Server\bin\Release\net9.0\publish\wwwroot\_content\*") do (
|
||||
set /A found=0
|
||||
for %%j in (%retain%) do (
|
||||
if "%%~nxi" == "%%j" set /A found=1
|
||||
)
|
||||
if not !found! == 1 rmdir /Q/S "%%i"
|
||||
)
|
||||
set retain=Oqtane.Modules.Admin.Login,Oqtane.Modules.HtmlText
|
||||
for /D %%i in ("..\Oqtane.Server\bin\Release\net8.0\publish\wwwroot\Modules\*") do (
|
||||
for /D %%i in ("..\Oqtane.Server\bin\Release\net9.0\publish\wwwroot\Modules\*") do (
|
||||
set /A found=0
|
||||
for %%j in (%retain%) do (
|
||||
if "%%~nxi" == "%%j" set /A found=1
|
||||
@ -21,18 +29,18 @@ if "%%~nxi" == "%%j" set /A found=1
|
||||
if not !found! == 1 rmdir /Q/S "%%i"
|
||||
)
|
||||
set retain=Oqtane.Themes.BlazorTheme,Oqtane.Themes.OqtaneTheme
|
||||
for /D %%i in ("..\Oqtane.Server\bin\Release\net8.0\publish\wwwroot\Themes\*") do (
|
||||
for /D %%i in ("..\Oqtane.Server\bin\Release\net9.0\publish\wwwroot\Themes\*") do (
|
||||
set /A found=0
|
||||
for %%j in (%retain%) do (
|
||||
if "%%~nxi" == "%%j" set /A found=1
|
||||
)
|
||||
if not !found! == 1 rmdir /Q/S "%%i"
|
||||
)
|
||||
del "..\Oqtane.Server\bin\Release\net8.0\publish\appsettings.json"
|
||||
ren "..\Oqtane.Server\bin\Release\net8.0\publish\appsettings.release.json" "appsettings.json"
|
||||
del "..\Oqtane.Server\bin\Release\net9.0\publish\appsettings.json"
|
||||
ren "..\Oqtane.Server\bin\Release\net9.0\publish\appsettings.release.json" "appsettings.json"
|
||||
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe ".\install.ps1"
|
||||
del "..\Oqtane.Server\bin\Release\net8.0\publish\appsettings.json"
|
||||
del "..\Oqtane.Server\bin\Release\net8.0\publish\web.config"
|
||||
del "..\Oqtane.Server\bin\Release\net9.0\publish\appsettings.json"
|
||||
del "..\Oqtane.Server\bin\Release\net9.0\publish\web.config"
|
||||
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe ".\upgrade.ps1"
|
||||
dotnet clean -c Release ..\Oqtane.Updater.sln
|
||||
dotnet build -c Release ..\Oqtane.Updater.sln
|
||||
|
@ -1 +1 @@
|
||||
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net8.0\publish\*" -DestinationPath "Oqtane.Framework.5.2.0.Upgrade.zip" -Force
|
||||
Compress-Archive -Path "..\Oqtane.Server\bin\Release\net9.0\publish\*" -DestinationPath "Oqtane.Framework.6.0.0.Upgrade.zip" -Force
|
||||
|
@ -179,10 +179,6 @@
|
||||
ManageScripts(resources, alias);
|
||||
|
||||
// generate scripts
|
||||
if (_renderMode == RenderModes.Interactive && _runtime == Runtimes.Server)
|
||||
{
|
||||
_scripts += CreateReconnectScript();
|
||||
}
|
||||
if (site.PwaIsEnabled && site.PwaAppIconFileId != null && site.PwaSplashIconFileId != null)
|
||||
{
|
||||
_scripts += CreatePWAScript(alias, site, route);
|
||||
@ -196,28 +192,29 @@
|
||||
_bodyResources += ParseScripts(site.BodyContent);
|
||||
|
||||
// set culture if not specified
|
||||
string culture = Context.Request.Cookies[CookieRequestCultureProvider.DefaultCookieName];
|
||||
if (culture == null)
|
||||
string cultureCookie = Context.Request.Cookies[Shared.CookieRequestCultureProvider.DefaultCookieName];
|
||||
if (cultureCookie == null)
|
||||
{
|
||||
// get default language for site
|
||||
if (site.Languages.Any())
|
||||
{
|
||||
// use default language if specified otherwise use first language in collection
|
||||
culture = (site.Languages.Where(l => l.IsDefault).SingleOrDefault() ?? site.Languages.First()).Code;
|
||||
cultureCookie = (site.Languages.Where(l => l.IsDefault).SingleOrDefault() ?? site.Languages.First()).Code;
|
||||
}
|
||||
else
|
||||
{
|
||||
culture = LocalizationManager.GetDefaultCulture();
|
||||
// fallback language
|
||||
cultureCookie = LocalizationManager.GetDefaultCulture();
|
||||
}
|
||||
SetLocalizationCookie(culture);
|
||||
// convert language code to culture cookie format (ie. "c=en|uic=en")
|
||||
cultureCookie = Shared.CookieRequestCultureProvider.MakeCookieValue(new Models.RequestCulture(cultureCookie));
|
||||
SetLocalizationCookie(cultureCookie);
|
||||
}
|
||||
|
||||
// set language for page
|
||||
if (!string.IsNullOrEmpty(culture))
|
||||
if (!string.IsNullOrEmpty(cultureCookie))
|
||||
{
|
||||
// localization cookie value in form of c=en|uic=en
|
||||
_language = culture.Split('|')[0];
|
||||
_language = _language.Replace("c=", "");
|
||||
_language = Shared.CookieRequestCultureProvider.ParseCookieValue(cultureCookie).Culture.Name;
|
||||
}
|
||||
|
||||
// create initial PageState
|
||||
@ -429,7 +426,10 @@
|
||||
new CookieOptions()
|
||||
{
|
||||
Expires = DateTimeOffset.UtcNow.AddYears(10),
|
||||
IsEssential = true
|
||||
IsEssential = true,
|
||||
SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Lax, // Set SameSite attribute
|
||||
Secure = true, // Ensure the cookie is only sent over HTTPS
|
||||
HttpOnly = true // Optional: Helps mitigate XSS attacks
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -491,35 +491,16 @@
|
||||
"</script>" + Environment.NewLine;
|
||||
}
|
||||
|
||||
private string CreateReconnectScript()
|
||||
{
|
||||
return Environment.NewLine +
|
||||
"<script>" + Environment.NewLine +
|
||||
" // Interactive Blazor Server Reconnect" + Environment.NewLine +
|
||||
" new MutationObserver((mutations, observer) => {" + Environment.NewLine +
|
||||
" if (document.querySelector('#components-reconnect-modal h5 a')) {" + Environment.NewLine +
|
||||
" async function attemptReload() {" + Environment.NewLine +
|
||||
" await fetch('');" + Environment.NewLine +
|
||||
" location.reload();" + Environment.NewLine +
|
||||
" }" + Environment.NewLine +
|
||||
" observer.disconnect();" + Environment.NewLine +
|
||||
" attemptReload();" + Environment.NewLine +
|
||||
" setInterval(attemptReload, 5000);" + Environment.NewLine +
|
||||
" }" + Environment.NewLine +
|
||||
" }).observe(document.body, { childList: true, subtree: true });" + Environment.NewLine +
|
||||
"</script>" + Environment.NewLine;
|
||||
}
|
||||
|
||||
private string CreateScrollPositionScript()
|
||||
{
|
||||
return Environment.NewLine +
|
||||
"<script>" + Environment.NewLine +
|
||||
" // Blazor Static Rendering Scroll Position" + Environment.NewLine +
|
||||
" window.interceptNavigation = () => {" + Environment.NewLine +
|
||||
" let currentUrl = window.location.href;" + Environment.NewLine +
|
||||
" let currentUrl = window.location.pathname;" + Environment.NewLine +
|
||||
" Blazor.addEventListener('enhancedload', () => {" + Environment.NewLine +
|
||||
" let newUrl = window.location.href;" + Environment.NewLine +
|
||||
" if (currentUrl != newUrl) {" + Environment.NewLine +
|
||||
" let newUrl = window.location.pathname;" + Environment.NewLine +
|
||||
" if (currentUrl !== newUrl || window.location.hash === '#top') {" + Environment.NewLine +
|
||||
" window.scrollTo({ top: 0, left: 0, behavior: 'instant' });" + Environment.NewLine +
|
||||
" }" + Environment.NewLine +
|
||||
" currentUrl = newUrl;" + Environment.NewLine +
|
||||
@ -531,9 +512,9 @@
|
||||
|
||||
private string ParseScripts(string content)
|
||||
{
|
||||
// iterate scripts
|
||||
var scripts = "";
|
||||
if (!string.IsNullOrEmpty(content))
|
||||
// in interactive render mode, parse scripts from content and inject into page
|
||||
if (_renderMode == RenderModes.Interactive && !string.IsNullOrEmpty(content))
|
||||
{
|
||||
var index = content.IndexOf("<script");
|
||||
while (index >= 0)
|
||||
@ -599,11 +580,21 @@
|
||||
}
|
||||
}
|
||||
|
||||
private void SetLocalizationCookie(string culture)
|
||||
private void SetLocalizationCookie(string cookieValue)
|
||||
{
|
||||
var cookieOptions = new Microsoft.AspNetCore.Http.CookieOptions
|
||||
{
|
||||
Expires = DateTimeOffset.UtcNow.AddYears(1),
|
||||
SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Lax, // Set SameSite attribute
|
||||
Secure = true, // Ensure the cookie is only sent over HTTPS
|
||||
HttpOnly = false // cookie is updated using JS Interop in Interactive render mode
|
||||
};
|
||||
|
||||
Context.Response.Cookies.Append(
|
||||
CookieRequestCultureProvider.DefaultCookieName,
|
||||
CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture)));
|
||||
Shared.CookieRequestCultureProvider.DefaultCookieName,
|
||||
cookieValue,
|
||||
cookieOptions
|
||||
);
|
||||
}
|
||||
|
||||
private async Task<List<Resource>> GetPageResources(Alias alias, Site site, Page page, List<Module> modules, int moduleid, string action)
|
||||
@ -631,6 +622,16 @@
|
||||
resources = AddResources(resources, obj.Resources, ResourceLevel.Page, alias, "Themes", type.Namespace, site.RenderMode);
|
||||
}
|
||||
}
|
||||
// theme settings components are dynamically loaded within the framework Page Management module
|
||||
if (page.Path == "admin/pages" && action.ToLower() == "edit" && theme != null && !string.IsNullOrEmpty(theme.ThemeSettingsType))
|
||||
{
|
||||
var settingsType = Type.GetType(theme.ThemeSettingsType);
|
||||
if (settingsType != null)
|
||||
{
|
||||
var objSettings = Activator.CreateInstance(settingsType) as IModuleControl;
|
||||
resources = AddResources(resources, objSettings.Resources, ResourceLevel.Module, alias, "Modules", settingsType.Namespace, site.RenderMode);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Module module in modules.Where(item => item.PageId == page.PageId || item.ModuleId == moduleid))
|
||||
{
|
||||
@ -673,25 +674,49 @@
|
||||
|
||||
// ensure component exists and implements IModuleControl
|
||||
module.ModuleType = "";
|
||||
Type moduletype = Type.GetType(typename, false, true); // case insensitive
|
||||
var moduletype = Type.GetType(typename, false, true); // case insensitive
|
||||
if (moduletype != null && moduletype.GetInterfaces().Contains(typeof(IModuleControl)))
|
||||
{
|
||||
module.ModuleType = Utilities.GetFullTypeName(moduletype.AssemblyQualifiedName); // get actual type name
|
||||
}
|
||||
if (moduletype != null && module.ModuleType != "")
|
||||
{
|
||||
var obj = Activator.CreateInstance(moduletype) as IModuleControl;
|
||||
if (obj != null)
|
||||
var moduleobject = Activator.CreateInstance(moduletype) as IModuleControl;
|
||||
if (moduleobject != null)
|
||||
{
|
||||
resources = AddResources(resources, obj.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace, site.RenderMode);
|
||||
resources = AddResources(resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace, site.RenderMode);
|
||||
|
||||
// settings components are dynamically loaded within the framework Settings module
|
||||
if (action.ToLower() == "settings" && module.ModuleDefinition != null)
|
||||
{
|
||||
// settings components are embedded within a framework settings module
|
||||
moduletype = Type.GetType(module.ModuleDefinition.ControlTypeTemplate.Replace(Constants.ActionToken, action), false, true);
|
||||
// module settings component
|
||||
var settingsType = "";
|
||||
if (!string.IsNullOrEmpty(module.ModuleDefinition.SettingsType))
|
||||
{
|
||||
// module settings type explicitly declared in IModule interface
|
||||
settingsType = module.ModuleDefinition.SettingsType;
|
||||
}
|
||||
else
|
||||
{
|
||||
// legacy support - module settings type determined by convention
|
||||
settingsType = module.ModuleDefinition.ControlTypeTemplate.Replace(Constants.ActionToken, action);
|
||||
}
|
||||
moduletype = Type.GetType(settingsType, false, true);
|
||||
if (moduletype != null)
|
||||
{
|
||||
obj = Activator.CreateInstance(moduletype) as IModuleControl;
|
||||
resources = AddResources(resources, obj.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace, site.RenderMode);
|
||||
moduleobject = Activator.CreateInstance(moduletype) as IModuleControl;
|
||||
resources = AddResources(resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace, site.RenderMode);
|
||||
}
|
||||
|
||||
// container settings component
|
||||
if (theme != null && !string.IsNullOrEmpty(theme.ContainerSettingsType))
|
||||
{
|
||||
moduletype = Type.GetType(theme.ContainerSettingsType);
|
||||
if (moduletype != null)
|
||||
{
|
||||
moduleobject = Activator.CreateInstance(moduletype) as IModuleControl;
|
||||
resources = AddResources(resources, moduleobject.Resources, ResourceLevel.Module, alias, "Modules", moduletype.Namespace, site.RenderMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,10 +17,10 @@ using Oqtane.Infrastructure;
|
||||
using Oqtane.Repository;
|
||||
using Oqtane.Extensions;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.Processing;
|
||||
using SixLabors.ImageSharp.Formats.Png;
|
||||
using System.Net.Http;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using System.IO.Compression;
|
||||
using Oqtane.Services;
|
||||
|
||||
// ReSharper disable StringIndexOfIsCultureSpecific.1
|
||||
|
||||
@ -37,7 +37,9 @@ namespace Oqtane.Controllers
|
||||
private readonly ILogManager _logger;
|
||||
private readonly Alias _alias;
|
||||
private readonly ISettingRepository _settingRepository;
|
||||
public FileController(IWebHostEnvironment environment, IFileRepository files, IFolderRepository folders, IUserPermissions userPermissions, ISettingRepository settingRepository, ISyncManager syncManager, ILogManager logger, ITenantManager tenantManager)
|
||||
private readonly IImageService _imageService;
|
||||
|
||||
public FileController(IWebHostEnvironment environment, IFileRepository files, IFolderRepository folders, IUserPermissions userPermissions, ISettingRepository settingRepository, ISyncManager syncManager, ILogManager logger, ITenantManager tenantManager, IImageService imageService)
|
||||
{
|
||||
_environment = environment;
|
||||
_files = files;
|
||||
@ -47,6 +49,7 @@ namespace Oqtane.Controllers
|
||||
_logger = logger;
|
||||
_alias = tenantManager.GetAlias();
|
||||
_settingRepository = settingRepository;
|
||||
_imageService = imageService;
|
||||
}
|
||||
|
||||
// GET: api/<controller>?folder=x
|
||||
@ -171,18 +174,30 @@ namespace Oqtane.Controllers
|
||||
{
|
||||
if (_userPermissions.IsAuthorized(User, folder.SiteId, EntityNames.Folder, file.FolderId, PermissionNames.Edit))
|
||||
{
|
||||
var filepath = _files.GetFilePath(file);
|
||||
if (System.IO.File.Exists(filepath))
|
||||
if (HasValidFileExtension(file.Name) && file.Name.IsPathOrFileValid())
|
||||
{
|
||||
file = CreateFile(file.Name, folder.FolderId, filepath);
|
||||
file = _files.AddFile(file);
|
||||
_syncManager.AddSyncEvent(_alias, EntityNames.File, file.FileId, SyncEventActions.Create);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Create, "File Added {File}", file);
|
||||
var filepath = _files.GetFilePath(file);
|
||||
if (System.IO.File.Exists(filepath))
|
||||
{
|
||||
file = CreateFile(file.Name, folder.FolderId, filepath);
|
||||
if (file != null)
|
||||
{
|
||||
file = _files.AddFile(file);
|
||||
_syncManager.AddSyncEvent(_alias, EntityNames.File, file.FileId, SyncEventActions.Create);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Create, "File Added {File}", file);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "File Does Not Exist At Path {FilePath}", filepath);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound;
|
||||
file = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "File Does Not Exist At Path {FilePath}", filepath);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound;
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "File Name Is Invalid Or Contains Invalid Extension {File}", file.Name);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
file = null;
|
||||
}
|
||||
}
|
||||
@ -213,29 +228,38 @@ namespace Oqtane.Controllers
|
||||
&& _userPermissions.IsAuthorized(User, file.Folder.SiteId, EntityNames.Folder, File.FolderId, PermissionNames.Edit) // ensure user had edit rights to original folder
|
||||
&& _userPermissions.IsAuthorized(User, file.Folder.SiteId, EntityNames.Folder, file.FolderId, PermissionNames.Edit)) // ensure user has edit rights to new folder
|
||||
{
|
||||
if (File.Name != file.Name || File.FolderId != file.FolderId)
|
||||
if (HasValidFileExtension(file.Name) && file.Name.IsPathOrFileValid())
|
||||
{
|
||||
file.Folder = _folders.GetFolder(file.FolderId, false);
|
||||
string folderpath = _folders.GetFolderPath(file.Folder);
|
||||
if (!Directory.Exists(folderpath))
|
||||
if (File.Name != file.Name || File.FolderId != file.FolderId)
|
||||
{
|
||||
Directory.CreateDirectory(folderpath);
|
||||
file.Folder = _folders.GetFolder(file.FolderId, false);
|
||||
string folderpath = _folders.GetFolderPath(file.Folder);
|
||||
if (!Directory.Exists(folderpath))
|
||||
{
|
||||
Directory.CreateDirectory(folderpath);
|
||||
}
|
||||
System.IO.File.Move(_files.GetFilePath(File), Path.Combine(folderpath, file.Name));
|
||||
}
|
||||
System.IO.File.Move(_files.GetFilePath(File), Path.Combine(folderpath, file.Name));
|
||||
}
|
||||
|
||||
var newfile = CreateFile(File.Name, file.Folder.FolderId, _files.GetFilePath(file));
|
||||
if (newfile != null)
|
||||
var newfile = CreateFile(File.Name, file.Folder.FolderId, _files.GetFilePath(file));
|
||||
if (newfile != null)
|
||||
{
|
||||
file.Extension = newfile.Extension;
|
||||
file.Size = newfile.Size;
|
||||
file.ImageWidth = newfile.ImageWidth;
|
||||
file.ImageHeight = newfile.ImageHeight;
|
||||
}
|
||||
|
||||
file = _files.UpdateFile(file);
|
||||
_syncManager.AddSyncEvent(_alias, EntityNames.File, file.FileId, SyncEventActions.Update);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Update, "File Updated {File}", file);
|
||||
}
|
||||
else
|
||||
{
|
||||
file.Extension = newfile.Extension;
|
||||
file.Size = newfile.Size;
|
||||
file.ImageWidth = newfile.ImageWidth;
|
||||
file.ImageHeight = newfile.ImageHeight;
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "File Name Is Invalid Or Contains Invalid Extension {File}", file.Name);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
file = null;
|
||||
}
|
||||
|
||||
file = _files.UpdateFile(file);
|
||||
_syncManager.AddSyncEvent(_alias, EntityNames.File, file.FileId, SyncEventActions.Update);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Update, "File Updated {File}", file);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -247,6 +271,57 @@ namespace Oqtane.Controllers
|
||||
return file;
|
||||
}
|
||||
|
||||
// PUT api/<controller>/unzip/5
|
||||
[HttpPut("unzip/{id}")]
|
||||
[Authorize(Roles = RoleNames.Admin)]
|
||||
public void Unzip(int id)
|
||||
{
|
||||
var zipfile = _files.GetFile(id, false);
|
||||
if (zipfile != null && zipfile.Folder.SiteId == _alias.SiteId && zipfile.Extension.ToLower() == "zip")
|
||||
{
|
||||
// extract files
|
||||
string folderpath = _folders.GetFolderPath(zipfile.Folder);
|
||||
using (ZipArchive archive = ZipFile.OpenRead(Path.Combine(folderpath, zipfile.Name)))
|
||||
{
|
||||
foreach (ZipArchiveEntry entry in archive.Entries)
|
||||
{
|
||||
if (HasValidFileExtension(entry.Name) && entry.Name.IsPathOrFileValid())
|
||||
{
|
||||
entry.ExtractToFile(Path.Combine(folderpath, entry.Name), true);
|
||||
var file = CreateFile(entry.Name, zipfile.Folder.FolderId, Path.Combine(folderpath, entry.Name));
|
||||
if (file != null)
|
||||
{
|
||||
if (file.FileId == 0)
|
||||
{
|
||||
file = _files.AddFile(file);
|
||||
}
|
||||
else
|
||||
{
|
||||
file = _files.UpdateFile(file);
|
||||
}
|
||||
_syncManager.AddSyncEvent(_alias, EntityNames.File, file.FileId, SyncEventActions.Create);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Create, "File Extracted {File}", file);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "File Name Is Invalid Or Contains Invalid Extension {File}", entry.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// delete zip file
|
||||
_files.DeleteFile(zipfile.FileId);
|
||||
System.IO.File.Delete(Path.Combine(folderpath, zipfile.Name));
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Zip File Removed {File}", zipfile);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized File Unzip Attempt {FileId}", id);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
}
|
||||
}
|
||||
|
||||
// DELETE api/<controller>/5
|
||||
[HttpDelete("{id}")]
|
||||
[Authorize(Roles = RoleNames.Registered)]
|
||||
@ -289,64 +364,55 @@ namespace Oqtane.Controllers
|
||||
folder = _folders.GetFolder(FolderId);
|
||||
}
|
||||
|
||||
var _UploadableFiles = _settingRepository.GetSetting(EntityNames.Site, _alias.SiteId, "UploadableFiles")?.SettingValue;
|
||||
_UploadableFiles = (string.IsNullOrEmpty(_UploadableFiles)) ? Constants.UploadableFiles : _UploadableFiles;
|
||||
|
||||
if (folder != null && folder.SiteId == _alias.SiteId && _userPermissions.IsAuthorized(User, PermissionNames.Edit, folder.PermissionList))
|
||||
{
|
||||
string folderPath = _folders.GetFolderPath(folder);
|
||||
CreateDirectory(folderPath);
|
||||
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
name = url.Substring(url.LastIndexOf("/", StringComparison.Ordinal) + 1);
|
||||
}
|
||||
// check for allowable file extensions
|
||||
if (!_UploadableFiles.Split(',').Contains(Path.GetExtension(name).ToLower().Replace(".", "")))
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Create, "File Could Not Be Downloaded From Url Due To Its File Extension {Url}", url);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Conflict;
|
||||
return file;
|
||||
}
|
||||
|
||||
if (!name.IsPathOrFileValid())
|
||||
if (HasValidFileExtension(name) && name.IsPathOrFileValid())
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Create, $"File Could Not Be Downloaded From Url Due To Its File Name Not Allowed {url}");
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Conflict;
|
||||
return file;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
string targetPath = Path.Combine(folderPath, name);
|
||||
|
||||
// remove file if it already exists
|
||||
if (System.IO.File.Exists(targetPath))
|
||||
try
|
||||
{
|
||||
System.IO.File.Delete(targetPath);
|
||||
}
|
||||
string folderPath = _folders.GetFolderPath(folder);
|
||||
CreateDirectory(folderPath);
|
||||
|
||||
using (var client = new HttpClient())
|
||||
{
|
||||
using (var stream = await client.GetStreamAsync(url))
|
||||
string targetPath = Path.Combine(folderPath, name);
|
||||
if (System.IO.File.Exists(targetPath))
|
||||
{
|
||||
using (var fileStream = new FileStream(targetPath, FileMode.CreateNew))
|
||||
System.IO.File.Delete(targetPath);
|
||||
}
|
||||
|
||||
using (var client = new HttpClient())
|
||||
{
|
||||
using (var stream = await client.GetStreamAsync(url))
|
||||
{
|
||||
await stream.CopyToAsync(fileStream);
|
||||
using (var fileStream = new FileStream(targetPath, FileMode.CreateNew))
|
||||
{
|
||||
await stream.CopyToAsync(fileStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
file = CreateFile(name, folder.FolderId, targetPath);
|
||||
if (file != null)
|
||||
file = CreateFile(name, folder.FolderId, targetPath);
|
||||
if (file != null)
|
||||
{
|
||||
file = _files.AddFile(file);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Create, "File Downloaded {File}", file);
|
||||
_syncManager.AddSyncEvent(_alias, EntityNames.File, file.FileId, SyncEventActions.Create);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
file = _files.AddFile(file);
|
||||
_syncManager.AddSyncEvent(_alias, EntityNames.File, file.FileId, SyncEventActions.Create);
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Create, ex, "File Could Not Be Downloaded From Url {Url} {Error}", url, ex.Message);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Create, ex, "File Could Not Be Downloaded From Url {Url} {Error}", url, ex.Message);
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "File Name Is Invalid Or Contains Invalid Extension {File}", name);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
file = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -361,29 +427,19 @@ namespace Oqtane.Controllers
|
||||
// POST api/<controller>/upload
|
||||
[EnableCors(Constants.MauiCorsPolicy)]
|
||||
[HttpPost("upload")]
|
||||
public async Task UploadFile(string folder, IFormFile formfile)
|
||||
public async Task<IActionResult> UploadFile(string folder, IFormFile formfile)
|
||||
{
|
||||
if (formfile == null || formfile.Length <= 0)
|
||||
{
|
||||
return;
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
// Get the UploadableFiles extensions
|
||||
string _UploadableFiles = _settingRepository.GetSetting(EntityNames.Site, _alias.SiteId, "UploadableFiles")?.SettingValue;
|
||||
_UploadableFiles = (string.IsNullOrEmpty(_UploadableFiles)) ? Constants.UploadableFiles : _UploadableFiles;
|
||||
|
||||
// ensure filename is valid
|
||||
string token = ".part_";
|
||||
if (!formfile.FileName.IsPathOrFileValid() || !formfile.FileName.Contains(token))
|
||||
if (!formfile.FileName.IsPathOrFileValid() || !formfile.FileName.Contains(token) || !HasValidFileExtension(formfile.FileName.Substring(0, formfile.FileName.IndexOf(token))))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// check for allowable file extensions (ignore token)
|
||||
var extension = Path.GetExtension(formfile.FileName.Substring(0, formfile.FileName.IndexOf(token))).Replace(".", "");
|
||||
if (!_UploadableFiles.Split(',').Contains(extension.ToLower()))
|
||||
{
|
||||
return;
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "File Name Is Invalid Or Contains Invalid Extension {File}", formfile.FileName);
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
string folderPath = "";
|
||||
@ -428,7 +484,7 @@ namespace Oqtane.Controllers
|
||||
{
|
||||
file = _files.UpdateFile(file);
|
||||
}
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Create, "File Upload Succeeded {File}", Path.Combine(folderPath, upload));
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Create, "File Uploaded {File}", Path.Combine(folderPath, upload));
|
||||
_syncManager.AddSyncEvent(_alias, EntityNames.File, file.FileId, SyncEventActions.Create);
|
||||
}
|
||||
}
|
||||
@ -438,6 +494,8 @@ namespace Oqtane.Controllers
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized File Upload Attempt {Folder} {File}", folder, formfile.FileName);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
}
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
private async Task<string> MergeFile(string folder, string filename)
|
||||
@ -459,7 +517,7 @@ namespace Oqtane.Controllers
|
||||
bool success = true;
|
||||
using (var stream = new FileStream(Path.Combine(folder, filename + ".tmp"), FileMode.Create))
|
||||
{
|
||||
foreach (string filepart in fileparts)
|
||||
foreach (string filepart in fileparts.Order())
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -625,22 +683,18 @@ namespace Oqtane.Controllers
|
||||
var filepath = _files.GetFilePath(file);
|
||||
if (System.IO.File.Exists(filepath))
|
||||
{
|
||||
// validation
|
||||
if (!Enum.TryParse(mode, true, out ResizeMode _)) mode = "crop";
|
||||
if (!Enum.TryParse(position, true, out AnchorPositionMode _)) position = "center";
|
||||
if (!Color.TryParseHex("#" + background, out _)) background = "transparent";
|
||||
if (!int.TryParse(rotate, out _)) rotate = "0";
|
||||
rotate = (int.Parse(rotate) < 0 || int.Parse(rotate) > 360) ? "0" : rotate;
|
||||
if (!bool.TryParse(recreate, out _)) recreate = "false";
|
||||
|
||||
string imagepath = filepath.Replace(Path.GetExtension(filepath), "." + width.ToString() + "x" + height.ToString() + ".png");
|
||||
string format = "png";
|
||||
|
||||
string imagepath = filepath.Replace(Path.GetExtension(filepath), "." + width.ToString() + "x" + height.ToString() + "." + format);
|
||||
if (!System.IO.File.Exists(imagepath) || bool.Parse(recreate))
|
||||
{
|
||||
// user has edit access to folder or folder supports the image size being created
|
||||
if (_userPermissions.IsAuthorized(User, PermissionNames.Edit, file.Folder.PermissionList) ||
|
||||
(!string.IsNullOrEmpty(file.Folder.ImageSizes) && (file.Folder.ImageSizes == "*" || file.Folder.ImageSizes.ToLower().Split(",").Contains(width.ToString() + "x" + height.ToString()))))
|
||||
{
|
||||
imagepath = CreateImage(filepath, width, height, mode, position, background, rotate, imagepath);
|
||||
imagepath = _imageService.CreateImage(filepath, width, height, mode, position, background, rotate, format, imagepath);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -687,70 +741,6 @@ namespace Oqtane.Controllers
|
||||
return System.IO.File.Exists(errorPath) ? PhysicalFile(errorPath, MimeUtilities.GetMimeType(errorPath)) : null;
|
||||
}
|
||||
|
||||
private string CreateImage(string filepath, int width, int height, string mode, string position, string background, string rotate, string imagepath)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var stream = new FileStream(filepath, FileMode.Open, FileAccess.Read))
|
||||
{
|
||||
stream.Position = 0;
|
||||
using (var image = Image.Load(stream))
|
||||
{
|
||||
int.TryParse(rotate, out int angle);
|
||||
Enum.TryParse(mode, true, out ResizeMode resizemode);
|
||||
Enum.TryParse(position, true, out AnchorPositionMode anchorpositionmode);
|
||||
|
||||
PngEncoder encoder;
|
||||
|
||||
if (background != "transparent")
|
||||
{
|
||||
image.Mutate(x => x
|
||||
.AutoOrient() // auto orient the image
|
||||
.Rotate(angle)
|
||||
.Resize(new ResizeOptions
|
||||
{
|
||||
Mode = resizemode,
|
||||
Position = anchorpositionmode,
|
||||
Size = new Size(width, height),
|
||||
PadColor = Color.ParseHex("#" + background)
|
||||
}));
|
||||
|
||||
encoder = new PngEncoder();
|
||||
}
|
||||
else
|
||||
{
|
||||
image.Mutate(x => x
|
||||
.AutoOrient() // auto orient the image
|
||||
.Rotate(angle)
|
||||
.Resize(new ResizeOptions
|
||||
{
|
||||
Mode = resizemode,
|
||||
Position = anchorpositionmode,
|
||||
Size = new Size(width, height)
|
||||
}));
|
||||
|
||||
encoder = new PngEncoder
|
||||
{
|
||||
ColorType = PngColorType.RgbWithAlpha,
|
||||
TransparentColorMode = PngTransparentColorMode.Preserve,
|
||||
BitDepth = PngBitDepth.Bit8,
|
||||
CompressionLevel = PngCompressionLevel.BestSpeed
|
||||
};
|
||||
}
|
||||
|
||||
image.Save(imagepath, encoder);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Security, ex, "Error Creating Image For File {FilePath} {Width} {Height} {Mode} {Rotate} {Error}", filepath, width, height, mode, rotate, ex.Message);
|
||||
imagepath = "";
|
||||
}
|
||||
|
||||
return imagepath;
|
||||
}
|
||||
|
||||
private string GetFolderPath(string folder)
|
||||
{
|
||||
return Utilities.PathCombine(_environment.ContentRootPath, folder);
|
||||
@ -838,10 +828,18 @@ namespace Oqtane.Controllers
|
||||
{
|
||||
_files.DeleteFile(file.FileId);
|
||||
}
|
||||
file = null;
|
||||
_logger.Log(LogLevel.Warning, this, LogFunction.Create, "File Exceeds Folder Capacity And Has Been Removed {Folder} {File}", folder, filepath);
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
private bool HasValidFileExtension(string fileName)
|
||||
{
|
||||
var _uploadableFiles = _settingRepository.GetSetting(EntityNames.Site, _alias.SiteId, "UploadableFiles")?.SettingValue;
|
||||
_uploadableFiles = (string.IsNullOrEmpty(_uploadableFiles)) ? Constants.UploadableFiles : _uploadableFiles;
|
||||
return _uploadableFiles.Split(',').Contains(Path.GetExtension(fileName).ToLower().Replace(".", ""));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -55,6 +55,10 @@ namespace Oqtane.Controllers
|
||||
else
|
||||
{
|
||||
languages = _languages.GetLanguages(SiteId).ToList();
|
||||
foreach (Language language in languages)
|
||||
{
|
||||
language.Name = CultureInfo.GetCultureInfo(language.Code).DisplayName;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(packagename))
|
||||
{
|
||||
foreach (var file in Directory.EnumerateFiles(Path.GetDirectoryName(Assembly.GetEntryAssembly().Location), $"{packagename}*{Constants.SatelliteAssemblyExtension}", SearchOption.AllDirectories))
|
||||
@ -85,6 +89,7 @@ namespace Oqtane.Controllers
|
||||
var language = _languages.GetLanguage(id);
|
||||
if (language != null && language.SiteId == _alias.SiteId)
|
||||
{
|
||||
language.Name = CultureInfo.GetCultureInfo(language.Code).DisplayName;
|
||||
return language;
|
||||
}
|
||||
else
|
||||
|
@ -351,9 +351,9 @@ namespace Oqtane.Controllers
|
||||
return new Dictionary<string, object>()
|
||||
{
|
||||
{ "FrameworkVersion", Constants.Version },
|
||||
{ "ClientReference", $"<Reference Include=\"Oqtane.Client\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net8.0\\Oqtane.Client.dll</HintPath></Reference>" },
|
||||
{ "ServerReference", $"<Reference Include=\"Oqtane.Server\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net8.0\\Oqtane.Server.dll</HintPath></Reference>" },
|
||||
{ "SharedReference", $"<Reference Include=\"Oqtane.Shared\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net8.0\\Oqtane.Shared.dll</HintPath></Reference>" },
|
||||
{ "ClientReference", $"<Reference Include=\"Oqtane.Client\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net9.0\\Oqtane.Client.dll</HintPath></Reference>" },
|
||||
{ "ServerReference", $"<Reference Include=\"Oqtane.Server\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net9.0\\Oqtane.Server.dll</HintPath></Reference>" },
|
||||
{ "SharedReference", $"<Reference Include=\"Oqtane.Shared\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net9.0\\Oqtane.Shared.dll</HintPath></Reference>" },
|
||||
};
|
||||
});
|
||||
}
|
||||
|
@ -8,10 +8,6 @@ using Oqtane.Infrastructure;
|
||||
using Oqtane.Repository;
|
||||
using Oqtane.Security;
|
||||
using System.Net;
|
||||
using System.Reflection.Metadata;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using System.Linq;
|
||||
|
||||
namespace Oqtane.Controllers
|
||||
{
|
||||
|
@ -109,7 +109,7 @@ namespace Oqtane.Controllers
|
||||
if (ModelState.IsValid && IsAuthorized(setting.EntityName, setting.EntityId, PermissionNames.Edit))
|
||||
{
|
||||
setting = _settings.AddSetting(setting);
|
||||
AddSyncEvent(setting.EntityName, setting.SettingId, SyncEventActions.Create);
|
||||
AddSyncEvent(setting.EntityName, setting.EntityId, setting.SettingId, SyncEventActions.Create);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Create, "Setting Added {Setting}", setting);
|
||||
}
|
||||
else
|
||||
@ -131,7 +131,7 @@ namespace Oqtane.Controllers
|
||||
if (ModelState.IsValid && setting.SettingId == id && IsAuthorized(setting.EntityName, setting.EntityId, PermissionNames.Edit))
|
||||
{
|
||||
setting = _settings.UpdateSetting(setting);
|
||||
AddSyncEvent(setting.EntityName, setting.SettingId, SyncEventActions.Update);
|
||||
AddSyncEvent(setting.EntityName, setting.EntityId, setting.SettingId, SyncEventActions.Update);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Setting Updated {Setting}", setting);
|
||||
}
|
||||
else
|
||||
@ -146,22 +146,60 @@ namespace Oqtane.Controllers
|
||||
return setting;
|
||||
}
|
||||
|
||||
// DELETE api/<controller>/5/xxx
|
||||
[HttpDelete("{id}/{entityName}")]
|
||||
public void Delete(string entityName, int id)
|
||||
// PUT api/<controller>/site/1/settingname/x/false
|
||||
[HttpPut("{entityName}/{entityId}/{settingName}/{settingValue}/{isPrivate}")]
|
||||
public void Put(string entityName, int entityId, string settingName, string settingValue, bool isPrivate)
|
||||
{
|
||||
Setting setting = _settings.GetSetting(entityName, id);
|
||||
if (IsAuthorized(setting.EntityName, setting.EntityId, PermissionNames.Edit))
|
||||
if (IsAuthorized(entityName, entityId, PermissionNames.Edit))
|
||||
{
|
||||
_settings.DeleteSetting(setting.EntityName, id);
|
||||
AddSyncEvent(setting.EntityName, setting.SettingId, SyncEventActions.Delete);
|
||||
Setting setting = _settings.GetSetting(entityName, entityId, settingName);
|
||||
if (setting == null)
|
||||
{
|
||||
setting = new Setting();
|
||||
setting.EntityName = entityName;
|
||||
setting.EntityId = entityId;
|
||||
setting.SettingName = settingName;
|
||||
setting.SettingValue = settingValue;
|
||||
setting.IsPrivate = isPrivate;
|
||||
setting = _settings.AddSetting(setting);
|
||||
AddSyncEvent(setting.EntityName, setting.EntityId, setting.SettingId, SyncEventActions.Create);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Setting Created {Setting}", setting);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (setting.SettingValue != settingValue || setting.IsPrivate != isPrivate)
|
||||
{
|
||||
setting.SettingValue = settingValue;
|
||||
setting.IsPrivate = isPrivate;
|
||||
setting = _settings.UpdateSetting(setting);
|
||||
AddSyncEvent(setting.EntityName, setting.EntityId, setting.SettingId, SyncEventActions.Update);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Update, "Setting Updated {Setting}", setting);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Update, "User Not Authorized To Add Or Update Setting {EntityName} {EntityId} {SettingName}", entityName, entityId, settingName);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
}
|
||||
}
|
||||
|
||||
// DELETE api/<controller>/site/1/settingname
|
||||
[HttpDelete("{entityName}/{entityId}/{settingName}")]
|
||||
public void Delete(string entityName, int entityId, string settingName)
|
||||
{
|
||||
Setting setting = _settings.GetSetting(entityName, entityId, settingName);
|
||||
if (setting != null && IsAuthorized(setting.EntityName, setting.EntityId, PermissionNames.Edit))
|
||||
{
|
||||
_settings.DeleteSetting(setting.EntityName, setting.SettingId);
|
||||
AddSyncEvent(setting.EntityName, setting.EntityId, setting.SettingId, SyncEventActions.Delete);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Delete, "Setting Deleted {Setting}", setting);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (entityName != EntityNames.Visitor)
|
||||
{
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Delete, "User Not Authorized To Delete Setting {Setting}", setting);
|
||||
_logger.Log(LogLevel.Error, this, LogFunction.Delete, "Setting Does Not Exist Or User Not Authorized To Delete Setting For Entity {EntityName} Id {EntityId} Name {SettingName}", entityName, entityId, settingName);
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
}
|
||||
}
|
||||
@ -300,7 +338,7 @@ namespace Oqtane.Controllers
|
||||
return filter;
|
||||
}
|
||||
|
||||
private void AddSyncEvent(string EntityName, int SettingId, string Action)
|
||||
private void AddSyncEvent(string EntityName, int EntityId, int SettingId, string Action)
|
||||
{
|
||||
_syncManager.AddSyncEvent(_alias, EntityName + "Setting", SettingId, Action);
|
||||
|
||||
@ -311,6 +349,9 @@ namespace Oqtane.Controllers
|
||||
case EntityNames.Site:
|
||||
_syncManager.AddSyncEvent(_alias, EntityNames.Site, _alias.SiteId, SyncEventActions.Refresh);
|
||||
break;
|
||||
case EntityNames.User:
|
||||
_syncManager.AddSyncEvent(_alias, EntityName, EntityId, SyncEventActions.Update);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -267,8 +267,8 @@ namespace Oqtane.Controllers
|
||||
return new Dictionary<string, object>()
|
||||
{
|
||||
{ "FrameworkVersion", Constants.Version },
|
||||
{ "ClientReference", $"<Reference Include=\"Oqtane.Client\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net8.0\\Oqtane.Client.dll</HintPath></Reference>" },
|
||||
{ "SharedReference", $"<Reference Include=\"Oqtane.Shared\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net8.0\\Oqtane.Shared.dll</HintPath></Reference>" },
|
||||
{ "ClientReference", $"<Reference Include=\"Oqtane.Client\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net9.0\\Oqtane.Client.dll</HintPath></Reference>" },
|
||||
{ "SharedReference", $"<Reference Include=\"Oqtane.Shared\"><HintPath>..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net9.0\\Oqtane.Shared.dll</HintPath></Reference>" },
|
||||
};
|
||||
});
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ using System.Threading.Tasks;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using Oqtane.Shared;
|
||||
using System;
|
||||
using System.Net;
|
||||
using Oqtane.Enums;
|
||||
using Oqtane.Infrastructure;
|
||||
@ -26,21 +25,21 @@ namespace Oqtane.Controllers
|
||||
private readonly IUserManager _userManager;
|
||||
private readonly ISiteRepository _sites;
|
||||
private readonly IUserPermissions _userPermissions;
|
||||
private readonly ISettingRepository _settings;
|
||||
private readonly IJwtManager _jwtManager;
|
||||
private readonly IFileRepository _files;
|
||||
private readonly ISettingRepository _settings;
|
||||
private readonly ILogManager _logger;
|
||||
|
||||
public UserController(IUserRepository users, ITenantManager tenantManager, IUserManager userManager, ISiteRepository sites, IUserPermissions userPermissions, ISettingRepository settings, IJwtManager jwtManager, IFileRepository files, ILogManager logger)
|
||||
public UserController(IUserRepository users, ITenantManager tenantManager, IUserManager userManager, ISiteRepository sites, IUserPermissions userPermissions, IJwtManager jwtManager, IFileRepository files, ISettingRepository settings, ILogManager logger)
|
||||
{
|
||||
_users = users;
|
||||
_tenantManager = tenantManager;
|
||||
_userManager = userManager;
|
||||
_sites = sites;
|
||||
_userPermissions = userPermissions;
|
||||
_settings = settings;
|
||||
_jwtManager = jwtManager;
|
||||
_files = files;
|
||||
_settings = settings;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@ -56,12 +55,6 @@ namespace Oqtane.Controllers
|
||||
{
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound;
|
||||
}
|
||||
else
|
||||
{
|
||||
List<Setting> settings = _settings.GetSettings(EntityNames.User, user.UserId).ToList();
|
||||
user.Settings = settings.Where(item => !item.IsPrivate || _userPermissions.GetUser(User).UserId == user.UserId)
|
||||
.ToDictionary(setting => setting.SettingName, setting => setting.SettingValue);
|
||||
}
|
||||
return Filter(user);
|
||||
}
|
||||
else
|
||||
@ -83,12 +76,6 @@ namespace Oqtane.Controllers
|
||||
{
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound;
|
||||
}
|
||||
else
|
||||
{
|
||||
List<Setting> settings = _settings.GetSettings(EntityNames.User, user.UserId).ToList();
|
||||
user.Settings = settings.Where(item => !item.IsPrivate || _userPermissions.GetUser(User).UserId == user.UserId)
|
||||
.ToDictionary(setting => setting.SettingName, setting => setting.SettingValue);
|
||||
}
|
||||
return Filter(user);
|
||||
}
|
||||
else
|
||||
@ -112,12 +99,6 @@ namespace Oqtane.Controllers
|
||||
{
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound;
|
||||
}
|
||||
else
|
||||
{
|
||||
List<Setting> settings = _settings.GetSettings(EntityNames.User, user.UserId).ToList();
|
||||
user.Settings = settings.Where(item => !item.IsPrivate || _userPermissions.GetUser(User).UserId == user.UserId)
|
||||
.ToDictionary(setting => setting.SettingName, setting => setting.SettingValue);
|
||||
}
|
||||
return Filter(user);
|
||||
}
|
||||
else
|
||||
@ -130,31 +111,58 @@ namespace Oqtane.Controllers
|
||||
|
||||
private User Filter(User user)
|
||||
{
|
||||
// clone object to avoid mutating cache
|
||||
User filtered = null;
|
||||
|
||||
if (user != null)
|
||||
{
|
||||
user.Password = "";
|
||||
user.IsAuthenticated = false;
|
||||
user.TwoFactorCode = "";
|
||||
user.TwoFactorExpiry = null;
|
||||
filtered = new User();
|
||||
|
||||
if (!_userPermissions.IsAuthorized(User, user.SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin) && User.Identity.Name?.ToLower() != user.Username.ToLower())
|
||||
// public properties
|
||||
filtered.SiteId = user.SiteId;
|
||||
filtered.UserId = user.UserId;
|
||||
filtered.Username = user.Username;
|
||||
filtered.DisplayName = user.DisplayName;
|
||||
|
||||
// restricted properties
|
||||
filtered.Password = "";
|
||||
filtered.TwoFactorCode = "";
|
||||
filtered.SecurityStamp = "";
|
||||
|
||||
// include private properties if authenticated user is accessing their own user account os is an administrator
|
||||
if (_userPermissions.IsAuthorized(User, user.SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin) || _userPermissions.GetUser(User).UserId == user.UserId)
|
||||
{
|
||||
user.Email = "";
|
||||
user.PhotoFileId = null;
|
||||
user.LastLoginOn = DateTime.MinValue;
|
||||
user.LastIPAddress = "";
|
||||
user.Roles = "";
|
||||
user.CreatedBy = "";
|
||||
user.CreatedOn = DateTime.MinValue;
|
||||
user.ModifiedBy = "";
|
||||
user.ModifiedOn = DateTime.MinValue;
|
||||
user.DeletedBy = "";
|
||||
user.DeletedOn = DateTime.MinValue;
|
||||
user.IsDeleted = false;
|
||||
user.TwoFactorRequired = false;
|
||||
filtered.Email = user.Email;
|
||||
filtered.PhotoFileId = user.PhotoFileId;
|
||||
filtered.LastLoginOn = user.LastLoginOn;
|
||||
filtered.LastIPAddress = user.LastIPAddress;
|
||||
filtered.TwoFactorRequired = false;
|
||||
filtered.Roles = user.Roles;
|
||||
filtered.CreatedBy = user.CreatedBy;
|
||||
filtered.CreatedOn = user.CreatedOn;
|
||||
filtered.ModifiedBy = user.ModifiedBy;
|
||||
filtered.ModifiedOn = user.ModifiedOn;
|
||||
filtered.DeletedBy = user.DeletedBy;
|
||||
filtered.DeletedOn = user.DeletedOn;
|
||||
filtered.IsDeleted = user.IsDeleted;
|
||||
}
|
||||
|
||||
// if authenticated user is accessing their own user account
|
||||
if (_userPermissions.GetUser(User).UserId == user.UserId)
|
||||
{
|
||||
// include all settings
|
||||
filtered.Settings = user.Settings;
|
||||
}
|
||||
else
|
||||
{
|
||||
// include only public settings
|
||||
filtered.Settings = _settings.GetSettings(EntityNames.User, user.UserId)
|
||||
.Where(item => !item.IsPrivate)
|
||||
.ToDictionary(setting => setting.SettingName, setting => setting.SettingValue);
|
||||
}
|
||||
}
|
||||
return user;
|
||||
|
||||
return filtered;
|
||||
}
|
||||
|
||||
// POST api/<controller>
|
||||
@ -167,11 +175,13 @@ namespace Oqtane.Controllers
|
||||
if (_userPermissions.IsAuthorized(User, user.SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin))
|
||||
{
|
||||
user.EmailConfirmed = true;
|
||||
user.IsAuthenticated = true;
|
||||
allowregistration = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
user.EmailConfirmed = false;
|
||||
user.IsAuthenticated = false;
|
||||
allowregistration = _sites.GetSite(user.SiteId).AllowRegistration;
|
||||
}
|
||||
|
||||
@ -253,8 +263,24 @@ namespace Oqtane.Controllers
|
||||
[Authorize]
|
||||
public async Task Logout([FromBody] User user)
|
||||
{
|
||||
await HttpContext.SignOutAsync(Constants.AuthenticationScheme);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Security, "User Logout {Username}", (user != null) ? user.Username : "");
|
||||
if (_userPermissions.GetUser(User).UserId == user.UserId)
|
||||
{
|
||||
await HttpContext.SignOutAsync(Constants.AuthenticationScheme);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Security, "User Logout {Username}", (user != null) ? user.Username : "");
|
||||
}
|
||||
}
|
||||
|
||||
// POST api/<controller>/logout
|
||||
[HttpPost("logouteverywhere")]
|
||||
[Authorize]
|
||||
public async Task LogoutEverywhere([FromBody] User user)
|
||||
{
|
||||
if (_userPermissions.GetUser(User).UserId == user.UserId)
|
||||
{
|
||||
await _userManager.LogoutUserEverywhere(user);
|
||||
await HttpContext.SignOutAsync(Constants.AuthenticationScheme);
|
||||
_logger.Log(LogLevel.Information, this, LogFunction.Security, "User Logout Everywhere {Username}", (user != null) ? user.Username : "");
|
||||
}
|
||||
}
|
||||
|
||||
// POST api/<controller>/verify
|
||||
@ -321,6 +347,13 @@ namespace Oqtane.Controllers
|
||||
return user;
|
||||
}
|
||||
|
||||
// GET api/<controller>/validate/x
|
||||
[HttpGet("validateuser")]
|
||||
public async Task<UserValidateResult> ValidateUser(string username, string email, string password)
|
||||
{
|
||||
return await _userManager.ValidateUser(username, email, password);
|
||||
}
|
||||
|
||||
// GET api/<controller>/validate/x
|
||||
[HttpGet("validate/{password}")]
|
||||
public async Task<bool> Validate(string password)
|
||||
@ -375,6 +408,7 @@ namespace Oqtane.Controllers
|
||||
}
|
||||
if (roles != "") roles = ";" + roles;
|
||||
user.Roles = roles;
|
||||
user.SecurityStamp = User.SecurityStamp();
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user