Merge pull request #84 from oqtane/dev

sync
This commit is contained in:
Shaun Walker 2021-03-10 17:01:33 -05:00 committed by GitHub
commit ed12194ea2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 192 additions and 114 deletions

2
.gitignore vendored
View File

@ -8,6 +8,8 @@ msbuild.binlog
*.binlog
*.nupkg
*.idea
Oqtane.Server/appsettings.json
Oqtane.Server/Data/*.mdf
Oqtane.Server/Data/*.ldf

View File

@ -36,7 +36,7 @@ else
</select>
</td>
<td>
<label>@Localizer["Rows:"] </label>
<label>@Localizer["Maximum Records:"] </label>
<select class="form-control" @onchange="(e => RowsChanged(e))">
<option value="10">10</option>
<option value="50">50</option>
@ -48,7 +48,7 @@ else
@if (_logs.Any())
{
<Pager Items="@_logs">
<Pager TableItem="Log" Items="@_logs">
<Header>
<th style="width: 1px;">&nbsp;</th>
<th>@Localizer["Date"]</th>
@ -141,6 +141,10 @@ else
private async Task GetLogs()
{
_logs = await LogService.GetLogsAsync(PageState.Site.SiteId, ((_level == "-") ? string.Empty : _level), ((_function == "-") ? string.Empty : _function), int.Parse(_rows));
await InvokeAsync(() =>
{
base.StateHasChanged();
});
}
private string GetClass(string function)

View File

@ -31,6 +31,12 @@
<td>@context.DeletedOn</td>
</Row>
</Pager>
@if (_pages.Any())
{
<div style="text-align:right;">
<ActionDialog Header="Delete All Pages" Message="@Localizer["Are You Sure You Wish To Permanently Delete All Pages?", "Delete All Pages"]" Action="Delete All Pages" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteAllPages())" ResourceKey="DeleteAllPages" />
</div>
}
}
</TabPanel>
<TabPanel Name="Modules" ResourceKey="Modules">
@ -59,6 +65,13 @@
<td>@context.DeletedOn</td>
</Row>
</Pager>
@if (_modules.Any())
{
<div style="text-align:right;">
<ActionDialog Header="Delete All Modules" Message="@Localizer["Are You Sure You Wish To Permanently Delete All Modules?", "Delete All Modules"]" Action="Delete All Modules" Security="SecurityAccessLevel.Admin" Class="btn btn-danger" OnClick="@(async () => await DeleteAllModules())" ResourceKey="DeleteAllModules" />
</div>
}
}
</TabPanel>
</TabStrip>
@ -126,6 +139,28 @@
}
}
private async Task DeleteAllPages()
{
try
{
foreach (Page page in _pages)
{
await PageService.DeletePageAsync(page.PageId);
await logger.LogInformation("Page Permanently Deleted {Page}", page);
}
await logger.LogInformation("Pages Permanently Deleted");
await Load();
StateHasChanged();
NavigationManager.NavigateTo(NavigateUrl());
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Permanently Deleting Pages {Error}", ex.Message);
AddModuleMessage(ex.Message, MessageType.Error);
}
}
private async Task RestoreModule(Module module)
{
try
@ -167,4 +202,31 @@
AddModuleMessage(Localizer["Error Permanently Deleting Module"], MessageType.Error);
}
}
private async Task DeleteAllModules()
{
try
{
foreach (Module module in _modules)
{
await PageModuleService.DeletePageModuleAsync(module.PageModuleId);
// check if there are any remaining module instances in the site
_modules = await ModuleService.GetModulesAsync(PageState.Site.SiteId);
if (!_modules.Exists(item => item.ModuleId == module.ModuleId))
{
await ModuleService.DeleteModuleAsync(module.ModuleId);
}
}
await logger.LogInformation("Modules Permanently Deleted");
await Load();
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Permanently Deleting Modules {Error}", ex.Message);
AddModuleMessage(Localizer["Error Permanently Deleting Modules"], MessageType.Error);
}
}
}

View File

@ -8,15 +8,15 @@
{
if (childPage.PageId == PageState.Page.PageId)
{
<a class="dropdown-item active" href="@GetUrl(childPage)" target="@GetTarget(childPage)">
<FontIcon Value="@childPage.Icon" />
@childPage.Name <span class="sr-only">(current)</span>
<a class="nav-link active px-3" href="@GetUrl(childPage)" target="@GetTarget(childPage)">
<span class="@childPage.Icon" aria-hidden="true" />
@childPage.Name <span class="sr-only">(current)</span>
</a>
}
else
{
<a class="dropdown-item" href="@GetUrl(childPage)" target="@GetTarget(childPage)">
<FontIcon Value="@childPage.Icon" />
<a class="nav-link px-3" href="@GetUrl(childPage)" target="@GetTarget(childPage)">
<span class="@childPage.Icon" aria-hidden="true" />
@childPage.Name
</a>
}
@ -34,7 +34,7 @@ else
{
<li class="nav-item active">
<a class="nav-link" href="@GetUrl(childPage)" target="@GetTarget(childPage)">
<FontIcon Value="@childPage.Icon" />
<span class="@childPage.Icon" aria-hidden="true" />
@childPage.Name <span class="sr-only">(current)</span>
</a>
</li>
@ -43,7 +43,7 @@ else
{
<li class="nav-item">
<a class="nav-link" href="@GetUrl(childPage)" target="@GetTarget(childPage)">
<FontIcon Value="@childPage.Icon" />
<span class="@childPage.Icon" aria-hidden="true" />
@childPage.Name
</a>
</li>
@ -55,7 +55,7 @@ else
{
<li class="nav-item dropdown active">
<a class="nav-link dropdown-toggle" href="@GetUrl(childPage)" target="@GetTarget(childPage)" id="@($"navbarDropdown{childPage.PageId}")" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<FontIcon Value="@childPage.Icon" />
<span class="@childPage.Icon" aria-hidden="true" />
@childPage.Name <span class="sr-only">(current)</span>
</a>
<MenuItemsHorizontal ParentPage="childPage" Pages="Pages" />
@ -65,7 +65,7 @@ else
{
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="@GetUrl(childPage)" target="@GetTarget(childPage)" id="@($"navbarDropdown{childPage.PageId}")" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<FontIcon Value="@childPage.Icon" />
<span class="@childPage.Icon" aria-hidden="true" />
@childPage.Name
</a>
<MenuItemsHorizontal ParentPage="childPage" Pages="Pages" />

View File

@ -9,7 +9,7 @@
{
<li class="nav-item px-3" style="margin-left: @(childPage.Level * 15)px;">
<a class="nav-link active" href="@GetUrl(childPage)" target="@GetTarget(childPage)">
<FontIcon Value="@childPage.Icon" />
<span class="@childPage.Icon" aria-hidden="true" />
@childPage.Name <span class="sr-only">(current)</span>
</a>
</li>
@ -18,7 +18,7 @@
{
<li class="nav-item px-3" style="margin-left: @(childPage.Level * 15)px;">
<a class="nav-link" href="@GetUrl(childPage)" target="@GetTarget(childPage)">
<FontIcon Value="@childPage.Icon" />
<span class="@childPage.Icon" aria-hidden="true" />
@childPage.Name
</a>
</li>
@ -38,7 +38,7 @@ else
{
<li class="nav-item px-3" style="margin-left: @(childPage.Level * 15)px;">
<a class="nav-link active" href="@GetUrl(childPage)" target="@GetTarget(childPage)">
<FontIcon Value="@childPage.Icon" />
<span class="@childPage.Icon" aria-hidden="true" />
@childPage.Name <span class="sr-only">(current)</span>
</a>
</li>
@ -47,7 +47,7 @@ else
{
<li class="nav-item px-3" style="margin-left: @(childPage.Level * 15)px;">
<a class="nav-link" href="@GetUrl(childPage)" target="@GetTarget(childPage)">
<FontIcon Value="@childPage.Icon" />
<span class="@childPage.Icon" aria-hidden="true" />
@childPage.Name
</a>
</li>

View File

@ -0,0 +1,15 @@
using System.Diagnostics.CodeAnalysis;
using Microsoft.EntityFrameworkCore;
namespace Oqtane.Extensions
{
public static class DbContextOptionsBuilderExtensions
{
public static DbContextOptionsBuilder UseOqtaneDatabase([NotNull] this DbContextOptionsBuilder optionsBuilder, string connectionString)
{
optionsBuilder.UseSqlServer(connectionString);
return optionsBuilder;
}
}
}

View File

@ -168,9 +168,10 @@ namespace Oqtane.Infrastructure
var dataDirectory = AppDomain.CurrentDomain.GetData("DataDirectory")?.ToString();
if (!Directory.Exists(dataDirectory)) Directory.CreateDirectory(dataDirectory);
using (var dbc = new DbContext(new DbContextOptionsBuilder().UseSqlServer(NormalizeConnectionString(install.ConnectionString)).Options))
var connectionString = NormalizeConnectionString(install.ConnectionString);
using (var dbc = new DbContext(new DbContextOptionsBuilder().UseOqtaneDatabase(connectionString).Options))
{
// create empty database if it does not exist
// create empty database if it does not exist
dbc.Database.EnsureCreated();
result.Success = true;
}
@ -235,7 +236,7 @@ namespace Oqtane.Infrastructure
if (!string.IsNullOrEmpty(install.TenantName) && !string.IsNullOrEmpty(install.Aliases))
{
using (var db = new InstallationContext(NormalizeConnectionString(_config.GetConnectionString(SettingKeys.ConnectionStringKey))))
using (var db = new InstallationContext(NormalizeConnectionString(_config.GetConnectionString(SettingKeys.ConnectionStringKey))))
{
Tenant tenant;
if (install.IsNewTenant)
@ -274,7 +275,7 @@ namespace Oqtane.Infrastructure
using (var scope = _serviceScopeFactory.CreateScope())
{
var upgrades = scope.ServiceProvider.GetRequiredService<IUpgradeManager>();
using (var db = new InstallationContext(NormalizeConnectionString(_config.GetConnectionString(SettingKeys.ConnectionStringKey))))
{
foreach (var tenant in db.Tenant.ToList())

View File

@ -4,11 +4,12 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Oqtane.Extensions;
using Oqtane.Models;
namespace Oqtane.Repository
{
public class DBContextBase : IdentityUserContext<IdentityUser>
public class DBContextBase : IdentityUserContext<IdentityUser>
{
private ITenantResolver _tenantResolver;
private IHttpContextAccessor _accessor;
@ -24,63 +25,16 @@ namespace Oqtane.Repository
var tenant = _tenantResolver.GetTenant();
if (tenant != null)
{
optionsBuilder.UseSqlServer(tenant.DBConnectionString
.Replace("|DataDirectory|", AppDomain.CurrentDomain.GetData("DataDirectory")?.ToString())
);
var connectionString = tenant.DBConnectionString
.Replace("|DataDirectory|", AppDomain.CurrentDomain.GetData("DataDirectory")?.ToString());
optionsBuilder.UseOqtaneDatabase(connectionString);
}
base.OnConfiguring(optionsBuilder);
}
public override int SaveChanges()
{
ChangeTracker.DetectChanges();
string username = "";
if (_accessor.HttpContext != null && _accessor.HttpContext.User.Identity.Name != null)
{
username = _accessor.HttpContext.User.Identity.Name;
}
DateTime date = DateTime.UtcNow;
var created = ChangeTracker.Entries()
.Where(x => x.State == EntityState.Added);
foreach(var item in created)
{
if (item.Entity is IAuditable)
{
item.CurrentValues[nameof(IAuditable.CreatedBy)] = username;
item.CurrentValues[nameof(IAuditable.CreatedOn)] = date;
}
}
var modified = ChangeTracker.Entries()
.Where(x => x.State == EntityState.Modified || x.State == EntityState.Added);
foreach (var item in modified)
{
if (item.Entity is IAuditable)
{
item.CurrentValues[nameof(IAuditable.ModifiedBy)] = username;
item.CurrentValues[nameof(IAuditable.ModifiedOn)] = date;
}
if (item.Entity is IDeletable && item.State != EntityState.Added)
{
if ((bool)item.CurrentValues[nameof(IDeletable.IsDeleted)]
&& !item.GetDatabaseValues().GetValue<bool>(nameof(IDeletable.IsDeleted)))
{
item.CurrentValues[nameof(IDeletable.DeletedBy)] = username;
item.CurrentValues[nameof(IDeletable.DeletedOn)] = date;
}
else if (!(bool)item.CurrentValues[nameof(IDeletable.IsDeleted)]
&& item.GetDatabaseValues().GetValue<bool>(nameof(IDeletable.IsDeleted)))
{
item.CurrentValues[nameof(IDeletable.DeletedBy)] = null;
item.CurrentValues[nameof(IDeletable.DeletedOn)] = null;
}
}
}
DbContextUtils.SaveChanges(this, _accessor);
return base.SaveChanges();
}

View File

@ -0,0 +1,65 @@
using System;
using System.Linq;
using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Oqtane.Models;
namespace Oqtane.Repository
{
public class DbContextUtils
{
public static void SaveChanges(DbContext context, IHttpContextAccessor accessor)
{
var changeTracker = context.ChangeTracker;
changeTracker.DetectChanges();
string username = "";
if (accessor.HttpContext != null && accessor.HttpContext.User.Identity.Name != null)
{
username = accessor.HttpContext.User.Identity.Name;
}
DateTime date = DateTime.UtcNow;
var created = changeTracker.Entries()
.Where(x => x.State == EntityState.Added);
foreach(var item in created)
{
if (item.Entity is IAuditable)
{
item.CurrentValues[nameof(IAuditable.CreatedBy)] = username;
item.CurrentValues[nameof(IAuditable.CreatedOn)] = date;
}
}
var modified = changeTracker.Entries()
.Where(x => x.State == EntityState.Modified || x.State == EntityState.Added);
foreach (var item in modified)
{
if (item.Entity is IAuditable)
{
item.CurrentValues[nameof(IAuditable.ModifiedBy)] = username;
item.CurrentValues[nameof(IAuditable.ModifiedOn)] = date;
}
if (item.Entity is IDeletable && item.State != EntityState.Added)
{
if ((bool)item.CurrentValues[nameof(IDeletable.IsDeleted)]
&& !item.GetDatabaseValues().GetValue<bool>(nameof(IDeletable.IsDeleted)))
{
item.CurrentValues[nameof(IDeletable.DeletedBy)] = username;
item.CurrentValues[nameof(IDeletable.DeletedOn)] = date;
}
else if (!(bool)item.CurrentValues[nameof(IDeletable.IsDeleted)]
&& item.GetDatabaseValues().GetValue<bool>(nameof(IDeletable.IsDeleted)))
{
item.CurrentValues[nameof(IDeletable.DeletedBy)] = null;
item.CurrentValues[nameof(IDeletable.DeletedOn)] = null;
}
}
}
}
}
}

View File

@ -1,10 +1,11 @@
using System.Diagnostics.CodeAnalysis;
using Microsoft.EntityFrameworkCore;
using Oqtane.Extensions;
using Oqtane.Models;
namespace Oqtane.Repository
{
public class InstallationContext : DbContext
{
private readonly string _connectionString;
@ -15,7 +16,7 @@ namespace Oqtane.Repository
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseSqlServer(_connectionString);
=> optionsBuilder.UseOqtaneDatabase(_connectionString);
public virtual DbSet<Alias> Alias { get; set; }
public virtual DbSet<Tenant> Tenant { get; set; }

View File

@ -4,13 +4,14 @@ using Microsoft.AspNetCore.Http;
using Microsoft.EntityFrameworkCore;
using Oqtane.Models;
using Microsoft.Extensions.Configuration;
using Oqtane.Extensions;
namespace Oqtane.Repository
{
public class MasterDBContext : DbContext
{
private IHttpContextAccessor _accessor;
private IConfiguration _configuration;
private readonly IHttpContextAccessor _accessor;
private readonly IConfiguration _configuration;
public MasterDBContext(DbContextOptions<MasterDBContext> options, IHttpContextAccessor accessor, IConfiguration configuration) : base(options)
{
@ -20,11 +21,12 @@ namespace Oqtane.Repository
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!string.IsNullOrEmpty(_configuration.GetConnectionString("DefaultConnection")))
if (!String.IsNullOrEmpty(_configuration.GetConnectionString("DefaultConnection")))
{
optionsBuilder.UseSqlServer(_configuration.GetConnectionString("DefaultConnection")
.Replace("|DataDirectory|", AppDomain.CurrentDomain.GetData("DataDirectory")?.ToString())
);
var connectionString = _configuration.GetConnectionString("DefaultConnection")
.Replace("|DataDirectory|", AppDomain.CurrentDomain.GetData("DataDirectory")?.ToString());
optionsBuilder.UseOqtaneDatabase(connectionString);
}
base.OnConfiguring(optionsBuilder);
}
@ -37,38 +39,7 @@ namespace Oqtane.Repository
public override int SaveChanges()
{
ChangeTracker.DetectChanges();
string username = "";
if (_accessor.HttpContext != null && _accessor.HttpContext.User.Identity.Name != null)
{
username = _accessor.HttpContext.User.Identity.Name;
}
DateTime date = DateTime.UtcNow;
var created = ChangeTracker.Entries()
.Where(x => x.State == EntityState.Added);
foreach (var item in created)
{
if (item.Entity is IAuditable)
{
item.CurrentValues[nameof(IAuditable.CreatedBy)] = username;
item.CurrentValues[nameof(IAuditable.CreatedOn)] = date;
}
}
var modified = ChangeTracker.Entries()
.Where(x => x.State == EntityState.Modified || x.State == EntityState.Added);
foreach (var item in modified)
{
if (item.Entity is IAuditable)
{
item.CurrentValues[nameof(IAuditable.ModifiedBy)] = username;
item.CurrentValues[nameof(IAuditable.ModifiedOn)] = date;
}
}
DbContextUtils.SaveChanges(this, _accessor);
return base.SaveChanges();
}

View File

@ -2,7 +2,6 @@
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<LangVersion>7.3</LangVersion>
<AddRazorSupportForMvc>true</AddRazorSupportForMvc>
<Version>1.0.0</Version>
<Product>[Owner].[Module]</Product>

View File

@ -16,6 +16,8 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{77EECA8C-B58E-469E-B8C5-D543AFC9A654}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
.gitignore = .gitignore
README.md = README.md
EndProjectSection
EndProject
Global

View File

@ -50,9 +50,11 @@ There is a separate [Documentation repository](https://github.com/oqtane/oqtane.
This project is a work in progress and the schedule for implementing enhancements is dependent upon the availability of community members who are willing/able to assist.
V.2.1.0 ( Q1 2021 )
- [ ] Cross Platform Database Support ( ie. SQLite ) - see [#964](https://github.com/oqtane/oqtane.framework/discussions/964)
- [ ] EF Core Migrations for Database Installation/Upgrade - see [#964](https://github.com/oqtane/oqtane.framework/discussions/964)
V.2.0.1 ( Feb 27, 2021 )
- [x] Complete Static Localization of Admin UI
- [ ] Cross Platform Database Support ( ie. SQLite )
- [ ] EF Core Migrations for Database Installation/Upgrade
V.2.0.0 ( released in conjuntion with .NET 5 on Nov 11, 2020 )
- [x] Migration to .NET 5