notification service and user management improvements

This commit is contained in:
Shaun Walker 2020-02-03 16:43:37 -05:00
parent d8d5e768b2
commit 0aed11e71c
50 changed files with 2077 additions and 284 deletions

View File

@ -0,0 +1,85 @@
@namespace Oqtane.Modules.Admin.Files
@inherits ModuleBase
@inject IFolderService FolderService
@inject NavigationManager NavigationManager
<table class="table table-borderless">
<tr>
<td>
<label for="Name" class="control-label">Name: </label>
</td>
<td>
<input class="form-control" @bind="@name" />
</td>
</tr>
<tr>
<td>
<label for="Name" class="control-label">Permissions: </label>
</td>
<td>
<PermissionGrid EntityName="Folder" PermissionNames="View,Edit" Permissions="@permissions" @ref="permissiongrid" />
</td>
</tr>
</table>
<button type="button" class="btn btn-success" @onclick="SaveFolder">Save</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">Cancel</NavLink>
<br />
<br />
<AuditInfo CreatedBy="@createdby" CreatedOn="@createdon" ModifiedBy="@modifiedby" ModifiedOn="@modifiedon"></AuditInfo>
@code {
public override SecurityAccessLevel SecurityAccessLevel { get { return SecurityAccessLevel.Admin; } }
int FolderId;
string name;
string permissions;
string createdby;
DateTime createdon;
string modifiedby;
DateTime modifiedon;
PermissionGrid permissiongrid;
protected override async Task OnInitializedAsync()
{
try
{
FolderId = Int32.Parse(PageState.QueryString["id"]);
Folder folder = await FolderService.GetFolderAsync(FolderId);
if (folder != null)
{
name = folder.Name;
permissions = folder.Permissions;
createdby = folder.CreatedBy;
createdon = folder.CreatedOn;
modifiedby = folder.ModifiedBy;
modifiedon = folder.ModifiedOn;
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Folder {FolderId} {Error}", FolderId, ex.Message);
AddModuleMessage("Error Loading Module", MessageType.Error);
}
}
private async Task SaveFolder()
{
try
{
Folder folder = await FolderService.GetFolderAsync(FolderId);
if (folder != null)
{
folder.Permissions = permissiongrid.GetPermissions();
await FolderService.UpdateFolderAsync(folder);
await logger.LogInformation("Folder Saved {Folder}", folder);
NavigationManager.NavigateTo(NavigateUrl(Reload.Site));
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Saving Folder {FolderId} {Error}", FolderId, ex.Message);
AddModuleMessage("Error Saving Module", MessageType.Error);
}
}
}

View File

@ -8,22 +8,23 @@
}
else
{
<Pager Items="@JobLogs">
<Header>
<th>Name</th>
<th>Status</th>
<th>Started</th>
<th>Finished</th>
<th>Notes</th>
</Header>
<Row>
<td>@context.Job.Name</td>
<td>@DisplayStatus(context.Job.IsExecuting, context.Succeeded)</td>
<td>@context.StartDate</td>
<td>@context.FinishDate</td>
<td><ActionDialog Header="Job Notes" Message="@context.Notes" Text="View" Security="SecurityAccessLevel.Host" /></td>
</Row>
</Pager>
<Pager Items="@JobLogs">
<Header>
<th>Name</th>
<th>Status</th>
<th>Started</th>
<th>Finished</th>
</Header>
<Row>
<td>@context.Job.Name</td>
<td>@DisplayStatus(context.Job.IsExecuting, context.Succeeded)</td>
<td>@context.StartDate</td>
<td>@context.FinishDate</td>
</Row>
<Detail>
<td colspan="4">@context.Notes</td>
</Detail>
</Pager>
}
@code {

View File

@ -5,6 +5,10 @@
@inject IUserService UserService
@inject IServiceProvider ServiceProvider
@if (Message != "")
{
<ModuleMessage Message="@Message" Type="@Type" />
}
<AuthorizeView>
<Authorizing>
<text>...</text>
@ -30,6 +34,8 @@
</div>
<button type="button" class="btn btn-primary" @onclick="Login">Login</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">Cancel</button>
<br /><br />
<button type="button" class="btn btn-secondary" @onclick="Forgot">Forgot Password</button>
</div>
</NotAuthorized>
</AuthorizeView>
@ -37,14 +43,35 @@
@code {
public override SecurityAccessLevel SecurityAccessLevel { get { return SecurityAccessLevel.Anonymous; } }
public string Username { get; set; } = "";
public string Password { get; set; } = "";
public bool Remember { get; set; } = false;
string ReturnUrl = "";
public string Message = "";
public MessageType Type = MessageType.Info;
public string Username = "";
public string Password = "";
public bool Remember = false;
protected override void OnInitialized()
{
if (PageState.QueryString.ContainsKey("returnurl"))
{
ReturnUrl = PageState.QueryString["returnurl"];
}
if (PageState.QueryString.ContainsKey("verified"))
{
if (PageState.QueryString["verified"] == "1")
{
Message = "User Account Verified Successfully. You Can Now Login With Your Username And Password Below.";
}
else
{
Message = "User Account Could Not Be Verified. Please Contact Your Administrator For Further Instructions.";
Type = MessageType.Warning;
}
}
}
private async Task Login()
{
string ReturnUrl = PageState.QueryString["returnurl"];
var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider.GetService(typeof(IdentityAuthenticationStateProvider));
if (authstateprovider == null)
{
@ -61,12 +88,12 @@
var interop = new Interop(jsRuntime);
string antiforgerytoken = await interop.GetElementByName("__RequestVerificationToken");
var fields = new { __RequestVerificationToken = antiforgerytoken, username = Username, password = Password, remember = Remember, returnurl = ReturnUrl };
await interop.SubmitForm("/login/", fields);
await interop.SubmitForm("/pages/login/", fields);
}
else
{
await logger.LogInformation("Login Failed For Username {Username}", Username);
AddModuleMessage("Login Failed. Please Remember That Passwords Are Case Sensitive.", MessageType.Error);
AddModuleMessage("Login Failed. Please Remember That Passwords Are Case Sensitive And User Accounts Require Email Verification When They Initially Created.", MessageType.Error);
}
}
else
@ -86,14 +113,36 @@
else
{
await logger.LogInformation("Login Failed For Username {Username}", Username);
AddModuleMessage("Login Failed. Please Remember That Passwords Are Case Sensitive.", MessageType.Error);
AddModuleMessage("Login Failed. Please Remember That Passwords Are Case Sensitive And User Accounts Require Verification When They Are Initially Created So You May Wish To Check Your Email.", MessageType.Error);
}
}
}
private void Cancel()
{
string ReturnUrl = PageState.QueryString["returnurl"];
NavigationManager.NavigateTo(ReturnUrl);
}
private async Task Forgot()
{
if (Username != "")
{
User user = await UserService.GetUserAsync(Username, PageState.Site.SiteId);
if (user != null)
{
await UserService.ForgotPasswordAsync(user);
Message = "Please Check The Email Address Associated To Your User Account For A Password Reset Notification";
}
else
{
Message = "User Does Not Exist";
Type = MessageType.Warning;
}
}
else
{
Message = "Please Enter The Username Related To Your Account And Then Click The Forgot Password Option";
}
StateHasChanged();
}
}

View File

@ -3,6 +3,11 @@
@inject NavigationManager NavigationManager
@inject IUserService UserService
@if (Message != "")
{
<ModuleMessage Message="@Message" Type="MessageType.Info" />
}
<div class="container">
<div class="form-group">
<label for="Username" class="control-label">Username: </label>
@ -13,43 +18,62 @@
<input type="password" class="form-control" placeholder="Password" @bind="@Password" />
</div>
<div class="form-group">
<label for="Username" class="control-label">Email: </label>
<input type="text" class="form-control" placeholder="Username" @bind="@Email" />
<label for="Password" class="control-label">Confirm Password: </label>
<input type="password" class="form-control" placeholder="Password" @bind="@Confirm" />
</div>
<button type="button" class="btn btn-primary" @onclick="RegisterUser">Register</button>
<div class="form-group">
<label for="Username" class="control-label">Email: </label>
<input type="text" class="form-control" placeholder="Email" @bind="@Email" />
</div>
<div class="form-group">
<label for="DisplayName" class="control-label">Full Name: </label>
<input type="text" class="form-control" placeholder="Full Name" @bind="@DisplayName" />
</div>
<button type="button" class="btn btn-primary" @onclick="Register">Register</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">Cancel</button>
</div>
@code {
public override SecurityAccessLevel SecurityAccessLevel { get { return SecurityAccessLevel.Anonymous; } }
string Message = "Please Note That Registration Requires A Valid Email Address In Order To Verify Your Identity";
string Username = "";
string Password = "";
string Confirm = "";
string Email = "";
string DisplayName = "";
private async Task RegisterUser()
private async Task Register()
{
try
{
if (Username != "" && Password != "" && Email != "")
Message = "";
if (Username != "" && Password != "" && Confirm != "" && Email != "")
{
User user = new User();
user.SiteId = PageState.Site.SiteId;
user.Username = Username;
user.DisplayName = Username;
user.Email = Email;
user.Password = Password;
user = await UserService.AddUserAsync(user);
if (user != null)
if (Password == Confirm)
{
await logger.LogInformation("User Created {Username} {Email}", Username, Email);
NavigationManager.NavigateTo(NavigateUrl(""));
User user = new User();
user.SiteId = PageState.Site.SiteId;
user.Username = Username;
user.DisplayName = (DisplayName == "" ? Username : DisplayName);
user.Email = Email;
user.Password = Password;
user = await UserService.AddUserAsync(user);
if (user != null)
{
await logger.LogInformation("User Created {Username} {Email}", Username, Email);
AddModuleMessage("User Account Created. Please Check Your Email For Verification Instructions.", MessageType.Info);
}
else
{
await logger.LogError("Error Adding User {Username} {Email}", Username, Email);
AddModuleMessage("Error Adding User. Please Ensure Password Meets Complexity Requirements And Username Is Not Already In Use.", MessageType.Error);
}
}
else
{
await logger.LogError("Error Adding User {Username} {Email}", Username, Email);
AddModuleMessage("Error Adding User. Please Ensure Password Meets Complexity Requirements And Username Is Not Already In Use.", MessageType.Error);
AddModuleMessage("Passwords Entered Do Not Match", MessageType.Warning);
}
}
else

View File

@ -0,0 +1,89 @@
@namespace Oqtane.Modules.Admin.Reset
@inherits ModuleBase
@inject NavigationManager NavigationManager
@inject IUserService UserService
<div class="container">
<div class="form-group">
<label for="Username" class="control-label">Username: </label>
<input type="text" class="form-control" placeholder="Username" @bind="@Username" readonly />
</div>
<div class="form-group">
<label for="Password" class="control-label">Password: </label>
<input type="password" class="form-control" placeholder="Password" @bind="@Password" />
</div>
<div class="form-group">
<label for="Password" class="control-label">Confirm Password: </label>
<input type="password" class="form-control" placeholder="Password" @bind="@Confirm" />
</div>
<button type="button" class="btn btn-primary" @onclick="Reset">Reset Password</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">Cancel</button>
</div>
@code {
public override SecurityAccessLevel SecurityAccessLevel { get { return SecurityAccessLevel.Anonymous; } }
string Username = "";
string Password = "";
string Confirm = "";
protected override void OnInitialized()
{
if (PageState.QueryString.ContainsKey("name") && PageState.QueryString.ContainsKey("token"))
{
Username = PageState.QueryString["name"];
}
else
{
NavigationManager.NavigateTo(NavigateUrl(""));
}
}
private async Task Reset()
{
try
{
if (Username != "" && Password != "" && Confirm != "")
{
if (Password == Confirm)
{
User user = new User();
user.SiteId = PageState.Site.SiteId;
user.Username = Username;
user.DisplayName = Username;
user.Password = Password;
user = await UserService.ResetPasswordAsync(user, PageState.QueryString["token"]);
if (user != null)
{
await logger.LogInformation("User Password Reset {Username}", Username);
NavigationManager.NavigateTo(NavigateUrl("login"));
}
else
{
await logger.LogError("Error Resetting User Password {Username}", Username);
AddModuleMessage("Error Resetting User Password. Please Ensure Password Meets Complexity Requirements.", MessageType.Error);
}
}
else
{
AddModuleMessage("Passwords Entered Do Not Match", MessageType.Warning);
}
}
else
{
AddModuleMessage("You Must Provide A Username, Password, and Email Address", MessageType.Warning);
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Resetting User Password {Username} {Error}", Username, ex.Message);
AddModuleMessage("Error Resetting User Password", MessageType.Error);
}
}
private void Cancel()
{
NavigationManager.NavigateTo(NavigateUrl(""));
}
}

View File

@ -101,7 +101,7 @@ else
<label for="Name" class="control-label">Host Username:</label>
</td>
<td>
<input class="form-control" @bind="@username" disabled />
<input class="form-control" @bind="@username" readonly />
</td>
</tr>
<tr>
@ -143,7 +143,7 @@ else
urls = PageState.Alias.Name;
themes = ThemeService.GetThemeTypes(PageState.Themes);
containers = ThemeService.GetContainerTypes(PageState.Themes);
username = PageState.User.Username;
username = Constants.HostUser;
}
private async void TenantChanged(ChangeEventArgs e)

View File

@ -4,6 +4,7 @@
@inject ISiteService SiteService
@inject IAliasService AliasService
@inject IThemeService ThemeService
@inject ISettingService SettingService
@if (themes == null)
{
@ -11,92 +12,141 @@
}
else
{
<table class="table table-borderless">
<tr>
<td>
<label for="Name" class="control-label">Name: </label>
</td>
<td>
<input class="form-control" @bind="@name" />
</td>
</tr>
<tr>
<td>
<label for="Name" class="control-label">Aliases: </label>
</td>
<td>
<textarea class="form-control" @bind="@urls" rows="3" />
</td>
</tr>
<tr>
<td>
<label for="Name" class="control-label">Logo: </label>
</td>
<td>
<input class="form-control" @bind="@logo" />
</td>
</tr>
<tr>
<td>
<label for="Name" class="control-label">Default Theme: </label>
</td>
<td>
<select class="form-control" @onchange="(e => ThemeChanged(e))">
<option value="">&lt;Select Theme&gt;</option>
@foreach (KeyValuePair<string, string> item in themes)
{
if (item.Key == themetype)
<table class="table table-borderless">
<tr>
<td>
<label for="Name" class="control-label">Name: </label>
</td>
<td>
<input class="form-control" @bind="@name" />
</td>
</tr>
<tr>
<td>
<label for="Name" class="control-label">Aliases: </label>
</td>
<td>
<textarea class="form-control" @bind="@urls" rows="3" />
</td>
</tr>
<tr>
<td>
<label for="Name" class="control-label">Logo: </label>
</td>
<td>
<input class="form-control" @bind="@logo" />
</td>
</tr>
<tr>
<td>
<label for="Name" class="control-label">Default Theme: </label>
</td>
<td>
<select class="form-control" @onchange="(e => ThemeChanged(e))">
<option value="">&lt;Select Theme&gt;</option>
@foreach (KeyValuePair<string, string> item in themes)
{
<option value="@item.Key" selected>@item.Value</option>
if (item.Key == themetype)
{
<option value="@item.Key" selected>@item.Value</option>
}
else
{
<option value="@item.Key">@item.Value</option>
}
}
else
</select>
</td>
</tr>
<tr>
<td>
<label for="Name" class="control-label">Default Layout: </label>
</td>
<td>
<select class="form-control" @bind="@layouttype">
<option value="">&lt;Select Layout&gt;</option>
@foreach (KeyValuePair<string, string> panelayout in panelayouts)
{
<option value="@item.Key">@item.Value</option>
<option value="@panelayout.Key">@panelayout.Value</option>
}
}
</select>
</td>
</tr>
<tr>
<td>
<label for="Name" class="control-label">Default Layout: </label>
</td>
<td>
<select class="form-control" @bind="@layouttype">
<option value="">&lt;Select Layout&gt;</option>
@foreach (KeyValuePair<string, string> panelayout in panelayouts)
{
<option value="@panelayout.Key">@panelayout.Value</option>
}
</select>
</td>
</tr>
<tr>
<td>
<label for="Name" class="control-label">Default Container: </label>
</td>
<td>
<select class="form-control" @bind="@containertype">
<option value="">&lt;Select Container&gt;</option>
@foreach (KeyValuePair<string, string> container in containers)
{
<option value="@container.Key">@container.Value</option>
}
</select>
</td>
</tr>
<tr>
<td>
<label for="Name" class="control-label">Is Deleted? </label>
</td>
<td>
<select class="form-control" @bind="@isdeleted">
<option value="True">Yes</option>
<option value="False">No</option>
</select>
</td>
</tr>
</table>
</select>
</td>
</tr>
<tr>
<td>
<label for="Name" class="control-label">Default Container: </label>
</td>
<td>
<select class="form-control" @bind="@containertype">
<option value="">&lt;Select Container&gt;</option>
@foreach (KeyValuePair<string, string> container in containers)
{
<option value="@container.Key">@container.Value</option>
}
</select>
</td>
</tr>
<tr>
<td>
<label for="Name" class="control-label">Is Deleted? </label>
</td>
<td>
<select class="form-control" @bind="@isdeleted">
<option value="True">Yes</option>
<option value="False">No</option>
</select>
</td>
</tr>
</table>
<a data-toggle="collapse" class="app-link-unstyled" href="#SMTP" aria-expanded="false" aria-controls="SMTP">
<h5>SMTP Settings</h5><hr class="app-rule" />
</a>
<div class="collapse" id="SMTP">
<table class="table table-borderless">
<tr>
<td>
<label for="Name" class="control-label">Host: </label>
</td>
<td>
<input class="form-control" @bind="@smtphost" />
</td>
</tr>
<tr>
<td>
<label for="Name" class="control-label">Port: </label>
</td>
<td>
<input class="form-control" @bind="@smtpport" />
</td>
</tr>
<tr>
<td>
<label for="Name" class="control-label">SSL Enabled: </label>
</td>
<td>
<input class="form-control" @bind="@smtpssl" />
</td>
</tr>
<tr>
<td>
<label for="Name" class="control-label">Username: </label>
</td>
<td>
<input class="form-control" @bind="@smtpusername" />
</td>
</tr>
<tr>
<td>
<label for="Name" class="control-label">Password: </label>
</td>
<td>
<input type="password" class="form-control" @bind="@smtppassword" />
</td>
</tr>
</table>
</div>
<button type="button" class="btn btn-success" @onclick="SaveSite">Save</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">Cancel</NavLink>
<br />
@ -121,6 +171,12 @@ else
string layouttype;
string containertype;
string smtphost = "";
string smtpport = "";
string smtpssl = "";
string smtpusername = "";
string smtppassword = "";
string createdby;
DateTime createdon;
string modifiedby;
@ -135,8 +191,8 @@ else
{
themes = ThemeService.GetThemeTypes(PageState.Themes);
containers = ThemeService.GetContainerTypes(PageState.Themes);
Alias = PageState.Aliases.Where(item => item.AliasId == Int32.Parse(PageState.QueryString["id"])).FirstOrDefault();
Alias = PageState.Aliases.Where(item => item.AliasId == Int32.Parse(PageState.QueryString["id"])).FirstOrDefault();
siteid = Alias.SiteId;
Site site = await SiteService.GetSiteAsync(siteid, Alias);
if (site != null)
@ -153,6 +209,13 @@ else
layouttype = site.DefaultLayoutType;
containertype = site.DefaultContainerType;
Dictionary<string, string> settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
smtphost = SettingService.GetSetting(settings, "SMTPHost", "");
smtpport = SettingService.GetSetting(settings, "SMTPPort", "");
smtpssl = SettingService.GetSetting(settings, "SMTPSSL", "");
smtpusername = SettingService.GetSetting(settings, "SMTPUsername", "");
smtppassword = SettingService.GetSetting(settings, "SMTPPassword", "");
createdby = site.CreatedBy;
createdon = site.CreatedOn;
modifiedby = site.ModifiedBy;
@ -229,6 +292,15 @@ else
await AliasService.AddAliasAsync(alias);
}
}
Dictionary<string, string> settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
SettingService.SetSetting(settings, "SMTPHost", smtphost);
SettingService.SetSetting(settings, "SMTPPort", smtpport);
SettingService.SetSetting(settings, "SMTPSSL", smtpssl);
SettingService.SetSetting(settings, "SMTPUsername", smtpusername);
SettingService.SetSetting(settings, "SMTPPassword", smtppassword);
await SettingService.UpdateModuleSettingsAsync(settings, site.SiteId);
await logger.LogInformation("Site Saved {Site}", site);
NavigationManager.NavigateTo(NavigateUrl(Reload.Site));

View File

@ -0,0 +1,100 @@
@namespace Oqtane.Modules.Admin.UserProfile
@inherits ModuleBase
@inject NavigationManager NavigationManager
@inject IUserRoleService UserRoleService
@inject INotificationService NotificationService
@if (PageState.User != null)
{
<table class="table table-borderless">
<tr>
<td>
<label for="Name" class="control-label">To: </label>
</td>
<td>
<select class="form-control" @bind="@userid">
<option value="-1">&lt;Select User&gt;</option>
@if (userroles != null)
{
foreach (UserRole userrole in userroles)
{
<option value="@userrole.UserId">@userrole.User.DisplayName</option>
}
}
</select>
</td>
</tr>
<tr>
<td>
<label for="Name" class="control-label">Subject: </label>
</td>
<td>
<input class="form-control" @bind="@subject" />
</td>
</tr>
<tr>
<td>
<label for="Name" class="control-label">Message: </label>
</td>
<td>
<textarea class="form-control" @bind="@body" rows="5" />
</td>
</tr>
</table>
<button type="button" class="btn btn-primary" @onclick="Send">Send</button>
<NavLink class="btn btn-secondary" href="@NavigateUrl()">Cancel</NavLink>
}
@code {
public override SecurityAccessLevel SecurityAccessLevel { get { return SecurityAccessLevel.View; } }
public override string Title { get { return "Send Notification"; } }
List<UserRole> userroles;
string userid = "-1";
string subject = "";
string body = "";
protected override async Task OnInitializedAsync()
{
try
{
userroles = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId);
userroles = userroles.Where(item => item.Role.Name == Constants.RegisteredRole || item.Role.Name == Constants.HostRole)
.OrderBy(item => item.User.DisplayName).ToList();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Users {Error}", ex.Message);
AddModuleMessage("Error Loading Users", MessageType.Error);
}
}
private async Task Send()
{
Notification notification = new Notification();
try
{
notification.SiteId = PageState.Site.SiteId;
notification.FromUserId = PageState.User.UserId;
notification.ToUserId = int.Parse(userid);
notification.ToEmail = "";
notification.Subject = subject;
notification.Body = body;
notification.ParentId = null;
notification.CreatedOn = DateTime.Now;
notification.IsDelivered = false;
notification.DeliveredOn = null;
notification = await NotificationService.AddNotificationAsync(notification);
await logger.LogInformation("Notification Created {Notification}", notification);
NavigationManager.NavigateTo(NavigateUrl());
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Adding Notification {Notification} {Error}", notification, ex.Message);
AddModuleMessage("Error Adding Notification", MessageType.Error);
}
}
}

View File

@ -4,69 +4,156 @@
@inject IUserService UserService
@inject IProfileService ProfileService
@inject ISettingService SettingService
@inject INotificationService NotificationService
@if (PageState.User != null && profiles != null)
{
<table class="table table-borderless">
<tr>
<td>
<label for="Name" class="control-label">Username: </label>
</td>
<td>
<input class="form-control" @bind="@username" readonly />
</td>
</tr>
<tr>
<td>
<label for="Name" class="control-label">Password: </label>
</td>
<td>
<input type="password" class="form-control" @bind="@password" />
</td>
</tr>
<tr>
<td>
<label for="Name" class="control-label">Email: </label>
</td>
<td>
<input class="form-control" @bind="@email" />
</td>
</tr>
<tr>
<td>
<label for="Name" class="control-label">Full Name: </label>
</td>
<td>
<input class="form-control" @bind="@displayname" />
</td>
</tr>
<div class="container-fluid">
<div class="form-group">
@foreach (Profile profile in profiles)
{
var p = profile;
if (p.Category != category)
{
<tr>
<th colspan="2" style="text-align: center;">
@p.Category
</th>
</tr>
category = p.Category;
}
<tr>
<td>
<label for="@p.Name" class="control-label">@p.Title: </label>
</td>
<td>
<input class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" placeholder="@p.Description" @onchange="(e => ProfileChanged(e, p.Name))" />
</td>
</tr>
}
</table>
<button type="button" class="btn btn-primary" @onclick="SaveUser">Save</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">Cancel</button>
<br />
<br />
<ul class="nav nav-tabs" role="tablist">
<li class="nav-item">
<a class="nav-link active" data-toggle="tab" href="#Profile" role="tab">
Profile
</a>
</li>
<li class="nav-item">
<a class="nav-link" data-toggle="tab" href="#Notifications" role="tab">
Notifications
</a>
</li>
</ul>
<div class="tab-content">
<div id="Profile" class="tab-pane fade show active" role="tabpanel">
<br />
<table class="table table-borderless">
<tr>
<td>
<label for="Name" class="control-label">Username: </label>
</td>
<td>
<input class="form-control" @bind="@username" readonly />
</td>
</tr>
<tr>
<td>
<label for="Name" class="control-label">Password: </label>
</td>
<td>
<input type="password" class="form-control" @bind="@password" />
</td>
</tr>
<tr>
<td>
<label for="Name" class="control-label">Confirm Password: </label>
</td>
<td>
<input type="password" class="form-control" @bind="@confirm" />
</td>
</tr>
<tr>
<td>
<label for="Name" class="control-label">Email: </label>
</td>
<td>
<input class="form-control" @bind="@email" />
</td>
</tr>
<tr>
<td>
<label for="Name" class="control-label">Full Name: </label>
</td>
<td>
<input class="form-control" @bind="@displayname" />
</td>
</tr>
@foreach (Profile profile in profiles)
{
var p = profile;
if (p.Category != category)
{
<tr>
<th colspan="2" style="text-align: center;">
@p.Category
</th>
</tr>
category = p.Category;
}
<tr>
<td>
<label for="@p.Name" class="control-label">@p.Title: </label>
</td>
<td>
<input class="form-control" maxlength="@p.MaxLength" value="@GetProfileValue(p.Name, p.DefaultValue)" placeholder="@p.Description" @onchange="(e => ProfileChanged(e, p.Name))" />
</td>
</tr>
}
</table>
<button type="button" class="btn btn-primary" @onclick="Save">Save</button>
<button type="button" class="btn btn-secondary" @onclick="Cancel">Cancel</button>
</div>
<div id="Notifications" class="tab-pane fade" role="tabpanel">
<br />
<ActionLink Action="Add" Text="Send Notification" Security="SecurityAccessLevel.View" EditMode="false" />
<br /><br />
@if (filter == "to")
{
<Pager Items="@notifications">
<Header>
<th>&nbsp;</th>
<th>&nbsp;</th>
<th>From</th>
<th>Subject</th>
<th>Received</th>
</Header>
<Row>
<td><ActionLink Action="View" Parameters="@($"id=" + context.NotificationId.ToString())" Security="SecurityAccessLevel.View" EditMode="false" /></td>
<td><ActionDialog Header="Delete Notification" Message="@("Are You Sure You Wish To Delete This Notification?")" Action="Delete" Security="SecurityAccessLevel.View" Class="btn btn-danger" OnClick="@(async () => await Delete(context))" EditMode="false" /></td>
<td>@(context.FromUser == null ? "System" : context.FromUser.DisplayName)</td>
<td>@context.Subject</td>
<td>@context.CreatedOn</td>
</Row>
<Detail>
<td colspan="2"></td>
<td colspan="3">@(context.Body.Length > 100 ? context.Body.Substring(0,100) : context.Body)</td>
</Detail>
</Pager>
}
else
{
<Pager Items="@notifications">
<Header>
<th>&nbsp;</th>
<th>&nbsp;</th>
<th>To</th>
<th>Subject</th>
<th>Sent</th>
</Header>
<Row>
<td><ActionLink Action="View" Parameters="@($"id=" + context.NotificationId.ToString())" Security="SecurityAccessLevel.View" EditMode="false" /></td>
<td><ActionDialog Header="Delete Notification" Message="@("Are You Sure You Wish To Delete This Notification?")" Action="Delete" Security="SecurityAccessLevel.View" Class="btn btn-danger" OnClick="@(async () => await Delete(context))" EditMode="false" /></td>
<td>@(context.ToUser == null ? context.ToEmail : context.ToUser.DisplayName)</td>
<td>@context.Subject</td>
<td>@context.CreatedOn</td>
</Row>
<Detail>
<td colspan="2"></td>
<td colspan="3">@(context.Body.Length > 100 ? context.Body.Substring(0,100) : context.Body)</td>
</Detail>
</Pager>
}
<br /><hr />
<select class="form-control" @onchange="(e => FilterChanged(e))">
<option value="to">Inbox</option>
<option value="from">Sent Items</option>
</select>
</div>
</div>
</div>
</div>
}
@code {
@ -74,11 +161,14 @@
string username = "";
string password = "";
string confirm = "";
string email = "";
string displayname = "";
List<Profile> profiles;
Dictionary<string, string> settings;
string category = "";
string filter = "to";
List<Notification> notifications;
protected override async Task OnInitializedAsync()
{
@ -91,6 +181,7 @@
displayname = PageState.User.DisplayName;
profiles = await ProfileService.GetProfilesAsync(ModuleState.SiteId);
settings = await SettingService.GetUserSettingsAsync(PageState.User.UserId);
await LoadNotificationsAsync();
}
else
{
@ -104,25 +195,45 @@
}
}
private async Task LoadNotificationsAsync()
{
notifications = await NotificationService.GetNotificationsAsync(PageState.Site.SiteId, filter, PageState.User.UserId);
notifications = notifications.Where(item => item.DeletedBy != PageState.User.Username).ToList();
}
private string GetProfileValue(string SettingName, string DefaultValue)
{
return SettingService.GetSetting(settings, SettingName, DefaultValue);
}
private async Task SaveUser()
private async Task Save()
{
try
{
User user = PageState.User;
user.Username = username;
user.Password = password;
user.Email = email;
user.DisplayName = displayname;
await UserService.UpdateUserAsync(user);
await SettingService.UpdateUserSettingsAsync(settings, PageState.User.UserId);
await logger.LogInformation("User Profile Saved");
if (password != "" && confirm != "" && email != "")
{
if (password == confirm)
{
User user = PageState.User;
user.Username = username;
user.Password = password;
user.Email = email;
user.DisplayName = (displayname == "" ? username : displayname);
await UserService.UpdateUserAsync(user);
await SettingService.UpdateUserSettingsAsync(settings, PageState.User.UserId);
await logger.LogInformation("User Profile Saved");
NavigationManager.NavigateTo(NavigateUrl(""));
NavigationManager.NavigateTo(NavigateUrl(""));
}
else
{
AddModuleMessage("Passwords Entered Do Not Match", MessageType.Warning);
}
}
else
{
AddModuleMessage("You Must Provide A Username, Password, and Email Address", MessageType.Warning);
}
}
catch (Exception ex)
{
@ -141,4 +252,36 @@
string value = (string)e.Value;
settings = SettingService.SetSetting(settings, SettingName, value);
}
private async Task Delete(Notification Notification)
{
try
{
if (!Notification.IsDeleted)
{
Notification.IsDeleted = true;
await NotificationService.UpdateNotificationAsync(Notification);
}
else
{
await NotificationService.DeleteNotificationAsync(Notification.NotificationId);
}
await logger.LogInformation("Notification Deleted {Notification}", Notification);
await LoadNotificationsAsync();
StateHasChanged();
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Deleting Notification {Notification} {Error}", Notification, ex.Message);
AddModuleMessage(ex.Message, MessageType.Error);
}
}
private async void FilterChanged(ChangeEventArgs e)
{
filter = (string)e.Value;
await LoadNotificationsAsync();
StateHasChanged();
}
}

View File

@ -0,0 +1,161 @@
@namespace Oqtane.Modules.Admin.UserProfile
@inherits ModuleBase
@inject NavigationManager NavigationManager
@inject IUserRoleService UserRoleService
@inject INotificationService NotificationService
@if (PageState.User != null)
{
<table class="table table-borderless">
<tr>
<td>
<label for="Name" class="control-label">@title: </label>
</td>
<td>
<select class="form-control" readonly @bind="userid">
<option value="-1">&lt;System&gt;</option>
@if (userroles != null)
{
foreach (UserRole userrole in userroles)
{
<option value="@userrole.UserId">@userrole.User.DisplayName</option>
}
}
</select>
</td>
</tr>
<tr>
<td>
<label for="Name" class="control-label">Subject: </label>
</td>
<td>
<input class="form-control" @bind="@subject" />
</td>
</tr>
@if (title == "From")
{
<tr>
<td>
<label for="Name" class="control-label">Date: </label>
</td>
<td>
<input class="form-control" @bind="@createdon" />
</td>
</tr>
}
<tr>
<td>
<label for="Name" class="control-label">Message: </label>
</td>
<td>
<textarea class="form-control" @bind="@body" rows="5" />
</td>
</tr>
</table>
@if (reply != "")
{
<button type="button" class="btn btn-primary" @onclick="Send">Send</button>
}
else
{
if (title == "From")
{
<button type="button" class="btn btn-primary" @onclick="Reply">Reply</button>
}
}
<NavLink class="btn btn-secondary" href="@NavigateUrl()">Cancel</NavLink>
<br />
<br />
<p>@reply</p>
}
@code {
public override SecurityAccessLevel SecurityAccessLevel { get { return SecurityAccessLevel.View; } }
public override string Title { get { return "View Notification"; } }
int notificationid;
string title = "";
List<UserRole> userroles;
string userid = "-1";
string subject = "";
string createdon = "";
string body = "";
string reply = "";
protected override async Task OnInitializedAsync()
{
try
{
userroles = await UserRoleService.GetUserRolesAsync(PageState.Site.SiteId);
userroles = userroles.Where(item => item.Role.Name == Constants.RegisteredRole || item.Role.Name == Constants.HostRole)
.OrderBy(item => item.User.DisplayName).ToList();
notificationid = Int32.Parse(PageState.QueryString["id"]);
Notification notification = await NotificationService.GetNotificationAsync(notificationid);
if (notification != null)
{
if (notification.ToUserId == PageState.User.UserId)
{
title = "From";
if (notification.FromUserId != null)
{
userid = notification.FromUserId.ToString();
}
}
else
{
title = "To";
if (notification.ToUserId != null)
{
userid = notification.ToUserId.ToString();
}
}
subject = notification.Subject;
createdon = notification.CreatedOn.ToString();
body = notification.Body;
}
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Loading Users {Error}", ex.Message);
AddModuleMessage("Error Loading Users", MessageType.Error);
}
}
private void Reply()
{
title = "To";
subject = "RE: " + subject;
reply = body;
StateHasChanged();
}
private async Task Send()
{
Notification notification = new Notification();
notification.SiteId = PageState.Site.SiteId;
notification.FromUserId = PageState.User.UserId;
notification.ToUserId = int.Parse(userid);
notification.ToEmail = "";
notification.Subject = subject;
notification.Body = body;
notification.ParentId = notificationid;
notification.CreatedOn = DateTime.Now;
notification.IsDelivered = false;
notification.DeliveredOn = null;
try
{
notification = await NotificationService.AddNotificationAsync(notification);
await logger.LogInformation("Notification Created {Notification}", notification);
NavigationManager.NavigateTo(NavigateUrl());
}
catch (Exception ex)
{
await logger.LogError(ex, "Error Adding Notification {Notification} {Error}", notification, ex.Message);
AddModuleMessage("Error Adding Notification", MessageType.Error);
}
}
}

View File

@ -24,6 +24,14 @@
<input type="password" class="form-control" @bind="@password" />
</td>
</tr>
<tr>
<td>
<label for="Name" class="control-label">Confirm Password: </label>
</td>
<td>
<input type="password" class="form-control" @bind="@confirm" />
</td>
</tr>
<tr>
<td>
<label for="Name" class="control-label">Email: </label>
@ -72,6 +80,7 @@
string username = "";
string password = "";
string confirm = "";
string email = "";
string displayname = "";
List<Profile> profiles;
@ -96,25 +105,39 @@
{
try
{
User user = new User();
user.SiteId = PageState.Site.SiteId;
user.Username = username;
user.Password = password;
user.Email = email;
user.DisplayName = string.IsNullOrWhiteSpace(user.DisplayName) ? user.Username : user.DisplayName;
user = await UserService.AddUserAsync(user);
if (user != null)
if (username != "" && password != "" && confirm != "" && email != "")
{
await SettingService.UpdateUserSettingsAsync(settings, user.UserId);
await logger.LogInformation("User Created {User}", user);
NavigationManager.NavigateTo(NavigateUrl());
if (password == confirm)
{
User 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 = 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("Error Adding User. Please Ensure Password Meets Complexity Requirements And Username Is Not Already In Use.", MessageType.Error);
}
}
else
{
AddModuleMessage("Passwords Entered Do Not Match", MessageType.Warning);
}
}
else
{
await logger.LogError("Error Adding User {Username} {Email}", username, email);
AddModuleMessage("Error Adding User. Please Ensure Password Meets Complexity Requirements And Username Is Not Already In Use.", MessageType.Error);
AddModuleMessage("You Must Provide A Username, Password, and Email Address", MessageType.Warning);
}
}
catch (Exception ex)

View File

@ -24,6 +24,14 @@
<input type="password" class="form-control" @bind="@password" />
</td>
</tr>
<tr>
<td>
<label for="Name" class="control-label">Confirm Password: </label>
</td>
<td>
<input type="password" class="form-control" @bind="@confirm" />
</td>
</tr>
<tr>
<td>
<label for="Name" class="control-label">Email: </label>
@ -87,6 +95,7 @@
int userid;
string username = "";
string password = "";
string confirm = "";
string email = "";
string displayname = "";
List<Profile> profiles;
@ -139,19 +148,33 @@
{
try
{
User user = await UserService.GetUserAsync(userid, PageState.Site.SiteId);
user.SiteId = PageState.Site.SiteId;
user.Username = username;
user.Password = password;
user.Email = email;
user.DisplayName = string.IsNullOrWhiteSpace(user.DisplayName) ? user.Username : user.DisplayName;
user.IsDeleted = (isdeleted == null ? true : Boolean.Parse(isdeleted));
if (username != "" && password != "" && confirm != "" && email != "")
{
if (password == confirm)
{
User user = await UserService.GetUserAsync(userid, PageState.Site.SiteId);
user.SiteId = PageState.Site.SiteId;
user.Username = username;
user.Password = password;
user.Email = email;
user.DisplayName = string.IsNullOrWhiteSpace(displayname) ? username : displayname;
user.IsDeleted = (isdeleted == null ? true : Boolean.Parse(isdeleted));
user = await UserService.UpdateUserAsync(user);
await SettingService.UpdateUserSettingsAsync(settings, user.UserId);
await logger.LogInformation("User Saved {User}", user);
user = await UserService.UpdateUserAsync(user);
await SettingService.UpdateUserSettingsAsync(settings, user.UserId);
await logger.LogInformation("User Saved {User}", user);
NavigationManager.NavigateTo(NavigateUrl());
NavigationManager.NavigateTo(NavigateUrl());
}
else
{
AddModuleMessage("Passwords Entered Do Not Match", MessageType.Warning);
}
}
else
{
AddModuleMessage("You Must Provide A Username, Password, and Email Address", MessageType.Warning);
}
}
catch (Exception ex)
{

View File

@ -50,10 +50,14 @@
[Parameter]
public string Class { get; set; } // optional
[Parameter]
public string EditMode { get; set; } // optional - specifies if a user must be in edit mode to see the action - default is true
[Parameter]
public Action OnClick { get; set; } // required if an Action is specified - executes a method in the calling component
bool visible = false;
bool editmode = true;
bool authorized = false;
protected override void OnParametersSet()
@ -66,13 +70,17 @@
{
Class = "btn btn-success";
}
if (!string.IsNullOrEmpty(EditMode))
{
editmode = bool.Parse(EditMode);
}
authorized = IsAuthorized();
}
private bool IsAuthorized()
{
bool authorized = false;
if (PageState.EditMode)
if (PageState.EditMode || !editmode)
{
SecurityAccessLevel security = SecurityAccessLevel.Host;
if (Security == null)

View File

@ -26,11 +26,15 @@
[Parameter]
public string Style { get; set; } // optional
[Parameter]
public string EditMode { get; set; } // optional - specifies if a user must be in edit mode to see the action - default is true
string text = "";
string url = "";
string parameters = "";
string classname = "btn btn-primary";
string style = "";
bool editmode = true;
bool authorized = false;
protected override void OnParametersSet()
@ -56,6 +60,11 @@
style = Style;
}
if (!string.IsNullOrEmpty(EditMode))
{
editmode = bool.Parse(EditMode);
}
url = EditUrl(Action, parameters);
authorized = IsAuthorized();
}
@ -63,7 +72,7 @@
private bool IsAuthorized()
{
bool authorized = false;
if (PageState.EditMode)
if (PageState.EditMode || !editmode)
{
SecurityAccessLevel security = SecurityAccessLevel.Host;
if (Security == null)

View File

@ -13,6 +13,10 @@
@foreach (var item in ItemList)
{
<tr>@Row(item)</tr>
@if (Detail != null)
{
<tr>@Detail(item)</tr>
}
}
</tbody>
</table>
@ -24,6 +28,10 @@
@foreach (var item in ItemList)
{
<div class="row">@Row(item)</div>
@if (Detail != null)
{
<div class="row">@Detail(item)</div>
}
}
</div>
}
@ -72,6 +80,9 @@
[Parameter]
public RenderFragment<TableItem> Row { get; set; }
[Parameter]
public RenderFragment<TableItem> Detail { get; set; }
[Parameter]
public IEnumerable<TableItem> Items { get; set; }

View File

@ -0,0 +1,93 @@
using Oqtane.Models;
using System.Threading.Tasks;
using System.Linq;
using System.Net.Http;
using Microsoft.AspNetCore.Components;
using System.Collections.Generic;
using Oqtane.Shared;
using System;
namespace Oqtane.Services
{
public class FolderService : ServiceBase, IFolderService
{
private readonly HttpClient http;
private readonly SiteState sitestate;
private readonly NavigationManager NavigationManager;
public FolderService(HttpClient http, SiteState sitestate, NavigationManager NavigationManager)
{
this.http = http;
this.sitestate = sitestate;
this.NavigationManager = NavigationManager;
}
private string apiurl
{
get { return CreateApiUrl(sitestate.Alias, NavigationManager.Uri, "Folder"); }
}
public async Task<List<Folder>> GetFoldersAsync(int SiteId)
{
List<Folder> folders = await http.GetJsonAsync<List<Folder>>(apiurl + "?siteid=" + SiteId.ToString());
folders = GetFoldersHierarchy(folders);
return folders;
}
public async Task<Folder> GetFolderAsync(int FolderId)
{
return await http.GetJsonAsync<Folder>(apiurl + "/" + FolderId.ToString());
}
public async Task<Folder> AddFolderAsync(Folder Folder)
{
return await http.PostJsonAsync<Folder>(apiurl, Folder);
}
public async Task<Folder> UpdateFolderAsync(Folder Folder)
{
return await http.PutJsonAsync<Folder>(apiurl + "/" + Folder.FolderId.ToString(), Folder);
}
public async Task UpdateFolderOrderAsync(int SiteId, int FolderId, int? ParentId)
{
await http.PutJsonAsync(apiurl + "/?siteid=" + SiteId.ToString() + "&folderid=" + FolderId.ToString() + "&parentid=" + ((ParentId == null) ? "" : ParentId.ToString()), null);
}
public async Task DeleteFolderAsync(int FolderId)
{
await http.DeleteAsync(apiurl + "/" + FolderId.ToString());
}
private static List<Folder> GetFoldersHierarchy(List<Folder> Folders)
{
List<Folder> hierarchy = new List<Folder>();
Action<List<Folder>, Folder> GetPath = null;
GetPath = (List<Folder> folders, Folder folder) =>
{
IEnumerable<Folder> children;
int level;
if (folder == null)
{
level = -1;
children = Folders.Where(item => item.ParentId == null);
}
else
{
level = folder.Level;
children = Folders.Where(item => item.ParentId == folder.FolderId);
}
foreach (Folder child in children)
{
child.Level = level + 1;
child.HasChildren = Folders.Where(item => item.ParentId == child.FolderId).Any();
hierarchy.Add(child);
GetPath(folders, child);
}
};
Folders = Folders.OrderBy(item => item.Order).ToList();
GetPath(Folders, null);
return hierarchy;
}
}
}

View File

@ -0,0 +1,16 @@
using Oqtane.Models;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Oqtane.Services
{
public interface IFolderService
{
Task<List<Folder>> GetFoldersAsync(int SiteId);
Task<Folder> GetFolderAsync(int FolderId);
Task<Folder> AddFolderAsync(Folder Folder);
Task<Folder> UpdateFolderAsync(Folder Folder);
Task UpdateFolderOrderAsync(int SiteId, int FolderId, int? ParentId);
Task DeleteFolderAsync(int FolderId);
}
}

View File

@ -0,0 +1,19 @@
using Oqtane.Models;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Oqtane.Services
{
public interface INotificationService
{
Task<List<Notification>> GetNotificationsAsync(int SiteId, string Direction, int UserId);
Task<Notification> GetNotificationAsync(int NotificationId);
Task<Notification> AddNotificationAsync(Notification Notification);
Task<Notification> UpdateNotificationAsync(Notification Notification);
Task DeleteNotificationAsync(int NotificationId);
}
}

View File

@ -23,5 +23,9 @@ namespace Oqtane.Services
Task<User> LoginUserAsync(User User, bool SetCookie, bool IsPersistent);
Task LogoutUserAsync(User User);
Task ForgotPasswordAsync(User User);
Task<User> ResetPasswordAsync(User User, string Token);
}
}

View File

@ -0,0 +1,55 @@
using Oqtane.Models;
using System.Threading.Tasks;
using System.Net.Http;
using Microsoft.AspNetCore.Components;
using Oqtane.Shared;
using System.Collections.Generic;
using System.Linq;
namespace Oqtane.Services
{
public class NotificationService : ServiceBase, INotificationService
{
private readonly HttpClient http;
private readonly SiteState sitestate;
private readonly NavigationManager NavigationManager;
public NotificationService(HttpClient http, SiteState sitestate, NavigationManager NavigationManager)
{
this.http = http;
this.sitestate = sitestate;
this.NavigationManager = NavigationManager;
}
private string apiurl
{
get { return CreateApiUrl(sitestate.Alias, NavigationManager.Uri, "Notification"); }
}
public async Task<List<Notification>> GetNotificationsAsync(int SiteId, string Direction, int UserId)
{
string querystring = "?siteid=" + SiteId.ToString() + "&direction=" + Direction.ToLower() + "&userid=" + UserId.ToString();
List<Notification> Notifications = await http.GetJsonAsync<List<Notification>>(apiurl + querystring);
return Notifications.OrderByDescending(item => item.CreatedOn).ToList();
}
public async Task<Notification> GetNotificationAsync(int NotificationId)
{
return await http.GetJsonAsync<Notification>(apiurl + "/" + NotificationId.ToString());
}
public async Task<Notification> AddNotificationAsync(Notification Notification)
{
return await http.PostJsonAsync<Notification>(apiurl, Notification);
}
public async Task<Notification> UpdateNotificationAsync(Notification Notification)
{
return await http.PutJsonAsync<Notification>(apiurl + "/" + Notification.NotificationId.ToString(), Notification);
}
public async Task DeleteNotificationAsync(int NotificationId)
{
await http.DeleteAsync(apiurl + "/" + NotificationId.ToString());
}
}
}

View File

@ -50,7 +50,7 @@ namespace Oqtane.Services
public async Task<Role> UpdateRoleAsync(Role Role)
{
return await http.PutJsonAsync<Role>(apiurl + "/" + Role.SiteId.ToString(), Role);
return await http.PutJsonAsync<Role>(apiurl + "/" + Role.RoleId.ToString(), Role);
}
public async Task DeleteRoleAsync(int RoleId)
{

View File

@ -86,5 +86,16 @@ namespace Oqtane.Services
// best practices recommend post is preferrable to get for logout
await http.PostJsonAsync(apiurl + "/logout", User);
}
public async Task ForgotPasswordAsync(User User)
{
await http.PostJsonAsync(apiurl + "/forgot", User);
}
public async Task<User> ResetPasswordAsync(User User, string Token)
{
return await http.PostJsonAsync<User>(apiurl + "/reset?token=" + Token, User);
}
}
}

View File

@ -59,7 +59,7 @@
<label for="Title" class="control-label" style="font-weight: bold">Username: </label>
</td>
<td>
<input type="text" class="form-control" @bind="@Username" />
<input type="text" class="form-control" readonly @bind="@Username" />
</td>
</tr>
<tr style="@IntegratedSecurityDisplay">
@ -85,7 +85,7 @@
<label for="Title" class="control-label" style="font-weight: bold">Username: </label>
</td>
<td>
<input type="text" class="form-control" @bind="@HostUsername" />
<input type="text" class="form-control" @bind="@HostUsername" readonly />
</td>
</tr>
<tr>
@ -96,6 +96,14 @@
<input type="password" class="form-control" @bind="@HostPassword" />
</td>
</tr>
<tr>
<td>
<label for="Title" class="control-label" style="font-weight: bold">Confirm Password: </label>
</td>
<td>
<input type="password" class="form-control" @bind="@ConfirmPassword" />
</td>
</tr>
<tr>
<td>
<label for="Title" class="control-label" style="font-weight: bold">Email: </label>
@ -123,8 +131,9 @@
private string DatabaseName = "Oqtane-" + DateTime.Now.ToString("yyyyMMddHHmm");
private string Username = "";
private string Password = "";
private string HostUsername = "host";
private string HostUsername = Constants.HostUser;
private string HostPassword = "";
private string ConfirmPassword = "";
private string HostEmail = "";
private string Message = "";
@ -145,7 +154,7 @@
private async Task Install()
{
if (HostUsername != "" & HostPassword.Length >= 6 & HostEmail != "")
if (HostUsername != "" && HostPassword.Length >= 6 && HostPassword == ConfirmPassword && HostEmail != "")
{
LoadingDisplay = "";
StateHasChanged();
@ -198,7 +207,7 @@
}
else
{
Message = "<div class=\"alert alert-danger\" role=\"alert\">Username And Email Must Be Provided And Password Must Be Greater Than 5 Characters</div>";
Message = "<div class=\"alert alert-danger\" role=\"alert\">Please Enter All Fields And Ensure Passwords Match And Are Greater Than 5 Characters In Length</div>";
}
}
}

View File

@ -56,6 +56,7 @@ namespace Oqtane.Client
services.AddScoped<ILogService, LogService>();
services.AddScoped<IJobService, JobService>();
services.AddScoped<IJobLogService, JobLogService>();
services.AddScoped<INotificationService, NotificationService>();
// dynamically register module contexts and repository services
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();

View File

@ -40,7 +40,7 @@
var interop = new Interop(jsRuntime);
string antiforgerytoken = await interop.GetElementByName("__RequestVerificationToken");
var fields = new { __RequestVerificationToken = antiforgerytoken, returnurl = (PageState.Alias.Path + "/" + PageState.Page.Path) };
await interop.SubmitForm("/logout/", fields);
await interop.SubmitForm("/pages/logout/", fields);
}
else
{

View File

@ -92,3 +92,21 @@ app {
height: 1px;
background-color: gray;
}
.app-link-unstyled, .app-link-unstyled:visited, .app-link-unstyled:hover, .app-link-unstyled:active, .app-link-unstyled:focus, .app-link-unstyled:active:hover {
font-style: inherit;
color: inherit;
background-color: transparent;
font-size: inherit;
text-decoration: none;
font-variant: inherit;
font-weight: inherit;
line-height: inherit;
font-family: inherit;
border-radius: inherit;
border: inherit;
outline: inherit;
box-shadow: inherit;
padding: inherit;
vertical-align: inherit;
}

View File

@ -0,0 +1,108 @@
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using Oqtane.Repository;
using Oqtane.Models;
using Oqtane.Shared;
using System.Linq;
using Oqtane.Infrastructure;
using Oqtane.Security;
namespace Oqtane.Controllers
{
[Route("{site}/api/[controller]")]
public class FolderController : Controller
{
private readonly IFolderRepository Folders;
private readonly IUserPermissions UserPermissions;
private readonly ILogManager logger;
public FolderController(IFolderRepository Folders, IUserPermissions UserPermissions, ILogManager logger)
{
this.Folders = Folders;
this.UserPermissions = UserPermissions;
this.logger = logger;
}
// GET: api/<controller>?siteid=x
[HttpGet]
public IEnumerable<Folder> Get(string siteid)
{
if (siteid == "")
{
return Folders.GetFolders();
}
else
{
return Folders.GetFolders(int.Parse(siteid));
}
}
// GET api/<controller>/5?userid=x
[HttpGet("{id}")]
public Folder Get(int id)
{
return Folders.GetFolder(id);
}
// POST api/<controller>
[HttpPost]
[Authorize(Roles = Constants.RegisteredRole)]
public Folder Post([FromBody] Folder Folder)
{
if (ModelState.IsValid && UserPermissions.IsAuthorized(User, "Edit", Folder.Permissions))
{
Folder = Folders.AddFolder(Folder);
logger.Log(LogLevel.Information, this, LogFunction.Create, "Folder Added {Folder}", Folder);
}
return Folder;
}
// PUT api/<controller>/5
[HttpPut("{id}")]
[Authorize(Roles = Constants.RegisteredRole)]
public Folder Put(int id, [FromBody] Folder Folder)
{
if (ModelState.IsValid && UserPermissions.IsAuthorized(User, "Folder", Folder.FolderId, "Edit"))
{
Folder = Folders.UpdateFolder(Folder);
logger.Log(LogLevel.Information, this, LogFunction.Update, "Folder Updated {Folder}", Folder);
}
return Folder;
}
// PUT api/<controller>/?siteid=x&folderid=y&parentid=z
[HttpPut]
[Authorize(Roles = Constants.RegisteredRole)]
public void Put(int siteid, int folderid, int? parentid)
{
if (UserPermissions.IsAuthorized(User, "Folder", folderid, "Edit"))
{
int order = 1;
List<Folder> folders = Folders.GetFolders(siteid).ToList();
foreach (Folder folder in folders.Where(item => item.ParentId == parentid).OrderBy(item => item.Order))
{
if (folder.Order != order)
{
folder.Order = order;
Folders.UpdateFolder(folder);
}
order += 2;
}
logger.Log(LogLevel.Information, this, LogFunction.Update, "Folder Order Updated {SiteId} {FolderId} {ParentId}", siteid, folderid, parentid);
}
}
// DELETE api/<controller>/5
[HttpDelete("{id}")]
[Authorize(Roles = Constants.RegisteredRole)]
public void Delete(int id)
{
if (UserPermissions.IsAuthorized(User, "Folder", id, "Edit"))
{
Folders.DeleteFolder(id);
logger.Log(LogLevel.Information, this, LogFunction.Delete, "Folder Deleted {FolderId}", id);
}
}
}
}

View File

@ -0,0 +1,110 @@
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using Oqtane.Repository;
using Oqtane.Models;
using Oqtane.Shared;
using Oqtane.Infrastructure;
using System.Security.Claims;
using Microsoft.AspNetCore.Http;
namespace Oqtane.Controllers
{
[Route("{site}/api/[controller]")]
public class NotificationController : Controller
{
private readonly INotificationRepository Notifications;
private readonly IHttpContextAccessor Accessor;
private readonly ILogManager logger;
public NotificationController(INotificationRepository Notifications, IHttpContextAccessor Accessor, ILogManager logger)
{
this.Notifications = Notifications;
this.Accessor = Accessor;
this.logger = logger;
}
// GET: api/<controller>?siteid=x&type=y&userid=z
[HttpGet]
[Authorize(Roles = Constants.RegisteredRole)]
public IEnumerable<Notification> Get(string siteid, string direction, string userid)
{
IEnumerable<Notification> notifications = null;
if (IsAuthorized(int.Parse(userid)))
{
if (direction == "to")
{
notifications = Notifications.GetNotifications(int.Parse(siteid), -1, int.Parse(userid));
}
else
{
notifications = Notifications.GetNotifications(int.Parse(siteid), int.Parse(userid), -1);
}
}
return notifications;
}
// GET api/<controller>/5
[HttpGet("{id}")]
[Authorize(Roles = Constants.RegisteredRole)]
public Notification Get(int id)
{
Notification Notification = Notifications.GetNotification(id);
if (!(IsAuthorized(Notification.FromUserId) || IsAuthorized(Notification.ToUserId)))
{
Notification = null;
}
return Notification;
}
// POST api/<controller>
[HttpPost]
[Authorize(Roles = Constants.RegisteredRole)]
public Notification Post([FromBody] Notification Notification)
{
if (IsAuthorized(Notification.FromUserId))
{
Notification = Notifications.AddNotification(Notification);
logger.Log(LogLevel.Information, this, LogFunction.Create, "Notification Added {Notification}", Notification);
}
return Notification;
}
// PUT api/<controller>/5
[HttpPut("{id}")]
[Authorize(Roles = Constants.RegisteredRole)]
public Notification Put(int id, [FromBody] Notification Notification)
{
if (IsAuthorized(Notification.FromUserId))
{
Notification = Notifications.UpdateNotification(Notification);
logger.Log(LogLevel.Information, this, LogFunction.Update, "Notification Updated {Folder}", Notification);
}
return Notification;
}
// DELETE api/<controller>/5
[HttpDelete("{id}")]
[Authorize(Roles = Constants.RegisteredRole)]
public void Delete(int id)
{
Notification Notification = Notifications.GetNotification(id);
if (IsAuthorized(Notification.FromUserId) || IsAuthorized(Notification.ToUserId))
{
Notifications.DeleteNotification(id);
logger.Log(LogLevel.Information, this, LogFunction.Delete, "Notification Deleted {NotificationId}", id);
}
}
private bool IsAuthorized(int? userid)
{
bool authorized = true;
if (userid != null)
{
authorized = (int.Parse(Accessor.HttpContext.User.FindFirst(ClaimTypes.PrimarySid).Value) == userid);
}
return authorized;
}
}
}

View File

@ -10,6 +10,9 @@ using System.Linq;
using System.Security.Claims;
using Oqtane.Shared;
using Oqtane.Infrastructure;
using System;
using Microsoft.AspNetCore.Http;
using System.Net;
namespace Oqtane.Controllers
{
@ -21,15 +24,19 @@ namespace Oqtane.Controllers
private readonly IUserRoleRepository UserRoles;
private readonly UserManager<IdentityUser> IdentityUserManager;
private readonly SignInManager<IdentityUser> IdentitySignInManager;
private readonly ITenantResolver Tenants;
private readonly INotificationRepository Notifications;
private readonly ILogManager logger;
public UserController(IUserRepository Users, IRoleRepository Roles, IUserRoleRepository UserRoles, UserManager<IdentityUser> IdentityUserManager, SignInManager<IdentityUser> IdentitySignInManager, ILogManager logger)
public UserController(IUserRepository Users, IRoleRepository Roles, IUserRoleRepository UserRoles, UserManager<IdentityUser> IdentityUserManager, SignInManager<IdentityUser> IdentitySignInManager, ITenantResolver Tenants, INotificationRepository Notifications, ILogManager logger)
{
this.Users = Users;
this.Roles = Roles;
this.UserRoles = UserRoles;
this.IdentityUserManager = IdentityUserManager;
this.IdentitySignInManager = IdentitySignInManager;
this.Tenants = Tenants;
this.Notifications = Notifications;
this.logger = logger;
}
@ -74,10 +81,11 @@ namespace Oqtane.Controllers
if (ModelState.IsValid)
{
int hostroleid = -1;
if (!Users.GetUsers().Any())
bool verified = true;
// users created by non-administrators must be verified
if (!base.User.IsInRole(Constants.AdminRole) && User.Username != Constants.HostUser)
{
hostroleid = Roles.GetRoles(User.SiteId, true).Where(item => item.Name == Constants.HostRole).FirstOrDefault().RoleId;
verified = false;
}
IdentityUser identityuser = await IdentityUserManager.FindByNameAsync(User.Username);
@ -86,14 +94,34 @@ namespace Oqtane.Controllers
identityuser = new IdentityUser();
identityuser.UserName = User.Username;
identityuser.Email = User.Email;
identityuser.EmailConfirmed = verified;
var result = await IdentityUserManager.CreateAsync(identityuser, User.Password);
if (result.Succeeded)
{
user = Users.AddUser(User);
// assign to host role if this is the initial installation
if (hostroleid != -1)
if (!verified)
{
Notification notification = new Notification();
notification.SiteId = User.SiteId;
notification.FromUserId = null;
notification.ToUserId = user.UserId;
notification.ToEmail = "";
notification.Subject = "User Account Verification";
string token = await IdentityUserManager.GenerateEmailConfirmationTokenAsync(identityuser);
string alias = Tenants.GetAlias().Path;
string url = HttpContext.Request.Scheme + "://" + HttpContext.Request.Host + "/pages/verify?name=" + User.Username + "&token=" + WebUtility.UrlEncode(token) + "&returnurl=" + (alias == "" ? "/" : alias);
notification.Body = "Dear " + User.DisplayName + ",\n\nIn Order To Complete The Registration Of Your User Account Please Click The Link Displayed Below:\n\n" + url + "\n\nThank You!";
notification.ParentId = null;
notification.CreatedOn = DateTime.Now;
notification.IsDelivered = false;
notification.DeliveredOn = null;
Notifications.AddNotification(notification);
}
// assign to host role if this is the host user ( initial installation )
if (User.Username == Constants.HostUser)
{
int hostroleid = Roles.GetRoles(User.SiteId, true).Where(item => item.Name == Constants.HostRole).FirstOrDefault().RoleId;
UserRole userrole = new UserRole();
userrole.UserId = user.UserId;
userrole.RoleId = hostroleid;
@ -112,7 +140,7 @@ namespace Oqtane.Controllers
}
}
if (user != null && hostroleid == -1)
if (user != null && User.Username != Constants.HostUser)
{
// add auto assigned roles to user for site
List<Role> roles = Roles.GetRoles(User.SiteId).Where(item => item.IsAutoAssigned == true).ToList();
@ -192,11 +220,18 @@ namespace Oqtane.Controllers
user = Users.GetUser(identityuser.UserName);
if (user != null)
{
user.IsAuthenticated = true;
logger.Log(LogLevel.Information, this, LogFunction.Security, "User Login Successful {Username}", User.Username);
if (SetCookie)
if (identityuser.EmailConfirmed)
{
await IdentitySignInManager.SignInAsync(identityuser, IsPersistent);
user.IsAuthenticated = true;
logger.Log(LogLevel.Information, this, LogFunction.Security, "User Login Successful {Username}", User.Username);
if (SetCookie)
{
await IdentitySignInManager.SignInAsync(identityuser, IsPersistent);
}
}
else
{
logger.Log(LogLevel.Information, this, LogFunction.Security, "User Not Verified {Username}", User.Username);
}
}
}
@ -219,6 +254,68 @@ namespace Oqtane.Controllers
logger.Log(LogLevel.Information, this, LogFunction.Security, "User Logout {Username}", User.Username);
}
// POST api/<controller>/forgot
[HttpPost("forgot")]
public async Task Forgot([FromBody] User User)
{
if (ModelState.IsValid)
{
IdentityUser identityuser = await IdentityUserManager.FindByNameAsync(User.Username);
if (identityuser != null)
{
Notification notification = new Notification();
notification.SiteId = User.SiteId;
notification.FromUserId = null;
notification.ToUserId = User.UserId;
notification.ToEmail = "";
notification.Subject = "User Password Reset";
string token = await IdentityUserManager.GeneratePasswordResetTokenAsync(identityuser);
string url = HttpContext.Request.Scheme + "://" + Tenants.GetAlias().Name + "/reset?name=" + User.Username + "&token=" + WebUtility.UrlEncode(token);
notification.Body = "Dear " + User.DisplayName + ",\n\nPlease Click The Link Displayed Below To Reset Your Password:\n\n" + url + "\n\nThank You!";
notification.ParentId = null;
notification.CreatedOn = DateTime.Now;
notification.IsDelivered = false;
notification.DeliveredOn = null;
Notifications.AddNotification(notification);
logger.Log(LogLevel.Information, this, LogFunction.Security, "Password Reset Notification Sent For {Username}", User.Username);
}
else
{
logger.Log(LogLevel.Error, this, LogFunction.Security, "Password Reset Notification Failed For {Username}", User.Username);
}
}
}
// POST api/<controller>/reset
[HttpPost("reset")]
public async Task<User> Reset([FromBody] User User, string token)
{
User user = null;
if (ModelState.IsValid)
{
IdentityUser identityuser = await IdentityUserManager.FindByNameAsync(User.Username);
if (identityuser != null && !string.IsNullOrEmpty(token))
{
var result = await IdentityUserManager.ResetPasswordAsync(identityuser, token, User.Password);
if (result.Succeeded)
{
user = User;
user.Password = "";
logger.Log(LogLevel.Information, this, LogFunction.Security, "Password Reset For {Username}", User.Username);
}
else
{
logger.Log(LogLevel.Error, this, LogFunction.Security, "Password Reset Failed For {Username}", User.Username);
}
}
else
{
logger.Log(LogLevel.Error, this, LogFunction.Security, "Password Reset Failed For {Username}", User.Username);
}
}
return user;
}
// GET api/<controller>/current
[HttpGet("authenticate")]
public User Authenticate()

View File

@ -0,0 +1,130 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Mail;
using Microsoft.Extensions.DependencyInjection;
using Oqtane.Models;
using Oqtane.Repository;
using Oqtane.Shared;
namespace Oqtane.Infrastructure
{
public class NotificationJob : HostedServiceBase
{
// JobType = "Oqtane.Infrastructure.NotificationJob, Oqtane.Server"
public NotificationJob(IServiceScopeFactory ServiceScopeFactory) : base(ServiceScopeFactory) {}
public override string ExecuteJob(IServiceProvider provider)
{
string log = "";
// iterate through aliases in this installation
var Aliases = provider.GetRequiredService<IAliasRepository>();
List<Alias> aliases = Aliases.GetAliases().ToList();
foreach (Alias alias in aliases)
{
// use the SiteState to set the Alias explicitly so the tenant can be resolved
var sitestate = provider.GetRequiredService<SiteState>();
sitestate.Alias = alias;
// get services which require tenant resolution
var Sites = provider.GetRequiredService<ISiteRepository>();
var Settings = provider.GetRequiredService<ISettingRepository>();
var Notifications = provider.GetRequiredService<INotificationRepository>();
// iterate through sites
List<Site> sites = Sites.GetSites().ToList();
foreach (Site site in sites)
{
log += "Processing Notifications For Site: " + site.Name + "\n\n";
// get site settings
List<Setting> sitesettings = Settings.GetSettings("Site", site.SiteId).ToList();
Dictionary<string, string> settings = GetSettings(sitesettings);
if (settings.ContainsKey("SMTPHost") && settings["SMTPHost"] != "")
{
// construct SMTP Client
var client = new SmtpClient()
{
DeliveryMethod = SmtpDeliveryMethod.Network,
UseDefaultCredentials = false,
Host = settings["SMTPHost"],
Port = int.Parse(settings["SMTPPort"]),
EnableSsl = bool.Parse(settings["SMTPSSL"])
};
if (settings["SMTPUsername"] != "" && settings["SMTPPassword"] != "")
{
client.Credentials = new NetworkCredential(settings["SMTPUsername"], settings["SMTPPassword"]);
}
// iterate through notifications
int sent = 0;
List<Notification> notifications = Notifications.GetNotifications(site.SiteId, -1, -1).ToList();
foreach (Notification notification in notifications)
{
MailMessage mailMessage = new MailMessage();
mailMessage.From = new MailAddress(settings["SMTPUsername"], site.Name);
if (notification.FromUserId != null)
{
mailMessage.Body = "From: " + notification.FromUser.DisplayName + "<" + notification.FromUser.Email + ">" + "\n";
}
else
{
mailMessage.Body = "From: " + site.Name + "\n";
}
mailMessage.Body += "Sent: " + notification.CreatedOn.ToString() + "\n";
if (notification.ToUserId != null)
{
mailMessage.To.Add(new MailAddress(notification.ToUser.Email, notification.ToUser.DisplayName));
mailMessage.Body += "To: " + notification.ToUser.DisplayName + "<" + notification.ToUser.Email + ">" + "\n";
}
else
{
mailMessage.To.Add(new MailAddress(notification.ToEmail));
mailMessage.Body += "To: " + notification.ToEmail + "\n";
}
mailMessage.Body += "Subject: " + notification.Subject + "\n\n";
mailMessage.Body += notification.Body;
// send mail
try
{
client.Send(mailMessage);
sent = sent++;
notification.IsDelivered = true;
notification.DeliveredOn = DateTime.Now;
Notifications.UpdateNotification(notification);
}
catch (Exception ex)
{
// error
log += ex.Message.ToString() + "\n\n";
}
}
log += "Notifications Delivered: " + sent.ToString() + "\n\n";
}
else
{
log += "SMTP Not Configured" + "\n\n";
}
}
}
return log;
}
private Dictionary<string, string> GetSettings(List<Setting> Settings)
{
Dictionary<string, string> dictionary = new Dictionary<string, string>();
foreach (Setting setting in Settings.OrderBy(item => item.SettingName).ToList())
{
dictionary.Add(setting.SettingName, setting.SettingValue);
}
return dictionary;
}
}
}

View File

@ -1,33 +0,0 @@
using System;
using System.Linq;
using Microsoft.Extensions.DependencyInjection;
using Oqtane.Models;
using Oqtane.Repository;
using Oqtane.Shared;
namespace Oqtane.Infrastructure
{
public class SampleJob : HostedServiceBase
{
// JobType = "Oqtane.Infrastructure.SampleJob, Oqtane.Server"
public SampleJob(IServiceScopeFactory ServiceScopeFactory) : base(ServiceScopeFactory) {}
public override string ExecuteJob(IServiceProvider provider)
{
// get the first alias for this installation
var Aliases = provider.GetRequiredService<IAliasRepository>();
Alias alias = Aliases.GetAliases().FirstOrDefault();
// use the SiteState to set the Alias explicitly so the tenant can be resolved
var sitestate = provider.GetRequiredService<SiteState>();
sitestate.Alias = alias;
// call a repository service which requires tenant resolution
var Sites = provider.GetRequiredService<ISiteRepository>();
Site site = Sites.GetSites().FirstOrDefault();
return "You Should Include Any Notes Related To The Execution Of The Schedule Job. This Job Simply Reports That The Default Site Is " + site.Name;
}
}
}

View File

@ -41,8 +41,8 @@
<PackageReference Include="Microsoft.AspNetCore.Blazor.Server" Version="3.1.0-preview4.19579.2" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="3.1.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.1" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.0.0-rc3" />
</ItemGroup>

View File

@ -1,3 +1,3 @@
@page "/login"
@page "/pages/login"
@namespace Oqtane.Pages
@model Oqtane.Pages.LoginModel

View File

@ -1,3 +1,3 @@
@page "/logout"
@page "/pages/logout"
@namespace Oqtane.Pages
@model Oqtane.Pages.LogoutModel

View File

@ -0,0 +1,3 @@
@page "/pages/verify"
@namespace Oqtane.Pages
@model Oqtane.Pages.VerifyModel

View File

@ -0,0 +1,42 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Oqtane.Models;
using Oqtane.Repository;
using System.Threading.Tasks;
namespace Oqtane.Pages
{
[AllowAnonymous]
public class VerifyModel : PageModel
{
private readonly IUserRepository Users;
private readonly UserManager<IdentityUser> IdentityUserManager;
public VerifyModel(IUserRepository Users, UserManager<IdentityUser> IdentityUserManager)
{
this.Users = Users;
this.IdentityUserManager = IdentityUserManager;
}
public async Task<IActionResult> OnGet(string name, string token, string returnurl)
{
int verified = 0;
IdentityUser identityuser = await IdentityUserManager.FindByNameAsync(name);
if (identityuser != null)
{
var result = await IdentityUserManager.ConfirmEmailAsync(identityuser, token);
if (result.Succeeded)
{
verified = 1;
}
}
if (!returnurl.StartsWith("/"))
{
returnurl += "/" + returnurl;
}
return Redirect(HttpContext.Request.Scheme + "://" + HttpContext.Request.Host + returnurl + "login?verified=" + verified.ToString());
}
}
}

View File

@ -17,6 +17,8 @@ namespace Oqtane.Repository
public virtual DbSet<Permission> Permission { get; set; }
public virtual DbSet<Setting> Setting { get; set; }
public virtual DbSet<Log> Log { get; set; }
public virtual DbSet<Notification> Notification { get; set; }
public virtual DbSet<Folder> Folder { get; set; }
public TenantDBContext(ITenantResolver TenantResolver, IHttpContextAccessor accessor) : base(TenantResolver, accessor)
{

View File

@ -0,0 +1,70 @@
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using Oqtane.Models;
namespace Oqtane.Repository
{
public class FolderRepository : IFolderRepository
{
private TenantDBContext db;
private readonly IPermissionRepository Permissions;
public FolderRepository(TenantDBContext context, IPermissionRepository Permissions)
{
db = context;
this.Permissions = Permissions;
}
public IEnumerable<Folder> GetFolders()
{
return db.Folder.ToList();
}
public IEnumerable<Folder> GetFolders(int SiteId)
{
IEnumerable<Permission> permissions = Permissions.GetPermissions(SiteId, "Folder").ToList();
IEnumerable<Folder> folders = db.Folder.Where(item => item.SiteId == SiteId);
foreach(Folder folder in folders)
{
folder.Permissions = Permissions.EncodePermissions(folder.FolderId, permissions);
}
return folders;
}
public Folder AddFolder(Folder Folder)
{
db.Folder.Add(Folder);
db.SaveChanges();
Permissions.UpdatePermissions(Folder.SiteId, "Folder", Folder.FolderId, Folder.Permissions);
return Folder;
}
public Folder UpdateFolder(Folder Folder)
{
db.Entry(Folder).State = EntityState.Modified;
db.SaveChanges();
Permissions.UpdatePermissions(Folder.SiteId, "Folder", Folder.FolderId, Folder.Permissions);
return Folder;
}
public Folder GetFolder(int FolderId)
{
Folder folder = db.Folder.Find(FolderId);
if (folder != null)
{
IEnumerable<Permission> permissions = Permissions.GetPermissions("Folder", folder.FolderId);
folder.Permissions = Permissions.EncodePermissions(folder.FolderId, permissions);
}
return folder;
}
public void DeleteFolder(int FolderId)
{
Folder Folder = db.Folder.Find(FolderId);
Permissions.DeletePermissions(Folder.SiteId, "Folder", FolderId);
db.Folder.Remove(Folder);
db.SaveChanges();
}
}
}

View File

@ -0,0 +1,15 @@
using System.Collections.Generic;
using Oqtane.Models;
namespace Oqtane.Repository
{
public interface IFolderRepository
{
IEnumerable<Folder> GetFolders();
IEnumerable<Folder> GetFolders(int SiteId);
Folder AddFolder(Folder Folder);
Folder UpdateFolder(Folder Folder);
Folder GetFolder(int FolderId);
void DeleteFolder(int FolderId);
}
}

View File

@ -0,0 +1,14 @@
using System.Collections.Generic;
using Oqtane.Models;
namespace Oqtane.Repository
{
public interface INotificationRepository
{
IEnumerable<Notification> GetNotifications(int SiteId, int FromUserId, int ToUserId);
Notification AddNotification(Notification Notification);
Notification UpdateNotification(Notification Notification);
Notification GetNotification(int NotificationId);
void DeleteNotification(int NotificationId);
}
}

View File

@ -0,0 +1,67 @@
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using Oqtane.Models;
namespace Oqtane.Repository
{
public class NotificationRepository : INotificationRepository
{
private TenantDBContext db;
public NotificationRepository(TenantDBContext context)
{
db = context;
}
public IEnumerable<Notification> GetNotifications(int SiteId, int FromUserId, int ToUserId)
{
if (ToUserId == -1 && FromUserId == -1)
{
return db.Notification
.Where(item => item.SiteId == SiteId)
.Where(item => item.IsDelivered == false)
.Include(item => item.FromUser)
.Include(item => item.ToUser)
.ToList();
}
else
{
return db.Notification
.Where(item => item.SiteId == SiteId)
.Where(item => item.ToUserId == ToUserId || ToUserId == -1)
.Where(item => item.FromUserId == FromUserId || FromUserId == -1)
.Include(item => item.FromUser)
.Include(item => item.ToUser)
.ToList();
}
}
public Notification AddNotification(Notification Notification)
{
db.Notification.Add(Notification);
db.SaveChanges();
return Notification;
}
public Notification UpdateNotification(Notification Notification)
{
db.Entry(Notification).State = EntityState.Modified;
db.SaveChanges();
return Notification;
}
public Notification GetNotification(int NotificationId)
{
return db.Notification.Find(NotificationId);
}
public void DeleteNotification(int NotificationId)
{
Notification Notification = db.Notification.Find(NotificationId);
db.Notification.Remove(Notification);
db.SaveChanges();
}
}
}

View File

@ -104,6 +104,9 @@ namespace Oqtane.Repository
SiteTemplate.Add(new PageTemplate { Name = "Register", Parent = "", Path = "register", Icon = "person", IsNavigation = false, IsPersonalizable = false, EditMode = false, PagePermissions = "[{\"PermissionName\":\"View\",\"Permissions\":\"All Users;Administrators\"},{\"PermissionName\":\"Edit\",\"Permissions\":\"Administrators\"}]", PageTemplateModules = new List<PageTemplateModule> {
new PageTemplateModule { ModuleDefinitionName = "Oqtane.Modules.Admin.Register, Oqtane.Client", Title = "User Registration", Pane = "Content", ModulePermissions = "[{\"PermissionName\":\"View\",\"Permissions\":\"All Users;Administrators\"},{\"PermissionName\":\"Edit\",\"Permissions\":\"Administrators\"}]", Content = "" }
}});
SiteTemplate.Add(new PageTemplate { Name = "Reset", Parent = "", Path = "reset", Icon = "person", IsNavigation = false, IsPersonalizable = false, EditMode = false, PagePermissions = "[{\"PermissionName\":\"View\",\"Permissions\":\"All Users;Administrators\"},{\"PermissionName\":\"Edit\",\"Permissions\":\"Administrators\"}]", PageTemplateModules = new List<PageTemplateModule> {
new PageTemplateModule { ModuleDefinitionName = "Oqtane.Modules.Admin.Reset, Oqtane.Client", Title = "Password Reset", Pane = "Content", ModulePermissions = "[{\"PermissionName\":\"View\",\"Permissions\":\"All Users;Administrators\"},{\"PermissionName\":\"Edit\",\"Permissions\":\"Administrators\"}]", Content = "" }
}});
SiteTemplate.Add(new PageTemplate { Name = "Profile", Parent = "", Path = "profile", Icon = "person", IsNavigation = false, IsPersonalizable = false, EditMode = false, PagePermissions = "[{\"PermissionName\":\"View\",\"Permissions\":\"All Users;Administrators\"},{\"PermissionName\":\"Edit\",\"Permissions\":\"Administrators\"}]", PageTemplateModules = new List<PageTemplateModule> {
new PageTemplateModule { ModuleDefinitionName = "Oqtane.Modules.Admin.UserProfile, Oqtane.Client", Title = "User Profile", Pane = "Content", ModulePermissions = "[{\"PermissionName\":\"View\",\"Permissions\":\"All Users;Administrators\"},{\"PermissionName\":\"Edit\",\"Permissions\":\"Administrators\"}]", Content = "" }
}});

View File

@ -23,7 +23,7 @@ namespace Oqtane.Repository
return db.UserRole
.Include(item => item.Role) // eager load roles
.Include(item => item.User) // eager load users
.Where(item => item.Role.SiteId == SiteId);
.Where(item => item.Role.SiteId == SiteId || item.Role.SiteId == null);
}
public IEnumerable<UserRole> GetUserRoles(int UserId, int SiteId)

View File

@ -214,7 +214,7 @@ CREATE TABLE [dbo].[Log] (
[PageId] [int] NULL,
[ModuleId] [int] NULL,
[UserId] [int] NULL,
[Url] [nvarchar](200) NOT NULL,
[Url] [nvarchar](2048) NOT NULL,
[Server] [nvarchar](200) NOT NULL,
[Category] [nvarchar](200) NOT NULL,
[Feature] [nvarchar](200) NOT NULL,
@ -232,6 +232,49 @@ CREATE TABLE [dbo].[Log] (
)
GO
CREATE TABLE [dbo].[Notification](
[NotificationId] [int] IDENTITY(1,1) NOT NULL,
[SiteId] [int] NOT NULL,
[FromUserId] [int] NULL,
[ToUserId] [int] NULL,
[ToEmail] [nvarchar](256) NOT NULL,
[Subject] [nvarchar](256) NOT NULL,
[Body] [nvarchar](max) NOT NULL,
[ParentId] [int] NULL,
[CreatedOn] [datetime] NOT NULL,
[IsDelivered] [bit] NOT NULL,
[DeliveredOn] [datetime] NULL,
[DeletedBy] [nvarchar](256) NULL,
[DeletedOn] [datetime] NULL,
[IsDeleted][bit] NOT NULL,
CONSTRAINT [PK_Notification] PRIMARY KEY CLUSTERED
(
[NotificationId] ASC
)
)
GO
CREATE TABLE [dbo].[Folder](
[FolderId] [int] IDENTITY(1,1) NOT NULL,
[SiteId] [int] NOT NULL,
[Path] [nvarchar](50) NOT NULL,
[Name] [nvarchar](50) NOT NULL,
[ParentId] [int] NULL,
[Order] [int] NOT NULL,
[CreatedBy] [nvarchar](256) NOT NULL,
[CreatedOn] [datetime] NOT NULL,
[ModifiedBy] [nvarchar](256) NOT NULL,
[ModifiedOn] [datetime] NOT NULL,
[DeletedBy] [nvarchar](256) NULL,
[DeletedOn] [datetime] NULL,
[IsDeleted][bit] NOT NULL,
CONSTRAINT [PK_Folder] PRIMARY KEY CLUSTERED
(
[FolderId] ASC
)
)
GO
CREATE TABLE [dbo].[HtmlText](
[HtmlTextId] [int] IDENTITY(1,1) NOT NULL,
[ModuleId] [int] NOT NULL,
@ -308,6 +351,16 @@ REFERENCES [dbo].[Site] ([SiteId])
ON DELETE CASCADE
GO
ALTER TABLE [dbo].[Notification] WITH CHECK ADD CONSTRAINT [FK_Notification_Site] FOREIGN KEY([SiteId])
REFERENCES [dbo].[Site] ([SiteId])
ON DELETE CASCADE
GO
ALTER TABLE [dbo].[Folder] WITH CHECK ADD CONSTRAINT [FK_Folder_Site] FOREIGN KEY([SiteId])
REFERENCES [dbo].[Site] ([SiteId])
ON DELETE CASCADE
GO
ALTER TABLE [dbo].[HtmlText] WITH CHECK ADD CONSTRAINT [FK_HtmlText_Module] FOREIGN KEY([ModuleId])
REFERENCES [dbo].[Module] ([ModuleId])
ON DELETE CASCADE
@ -360,3 +413,9 @@ CREATE UNIQUE NONCLUSTERED INDEX IX_UserRole ON dbo.UserRole
) ON [PRIMARY]
GO
CREATE UNIQUE NONCLUSTERED INDEX IX_Folder ON dbo.Folder
(
SiteId,
[Path]
) ON [PRIMARY]
GO

View File

@ -139,7 +139,7 @@ GO
SET IDENTITY_INSERT [dbo].[Job] ON
GO
INSERT [dbo].[Job] ([JobId], [Name], [JobType], [Frequency], [Interval], [StartDate], [EndDate], [IsEnabled], [IsStarted], [IsExecuting], [NextExecution], [RetentionHistory], [CreatedBy], [CreatedOn], [ModifiedBy], [ModifiedOn])
VALUES (1, N'Sample Daily Job', N'Oqtane.Infrastructure.SampleJob, Oqtane.Server', N'd', 1, null, null, 1, 0, 0, null, 10, '', getdate(), '', getdate())
VALUES (1, N'Notification Job', N'Oqtane.Infrastructure.NotificationJob, Oqtane.Server', N'm', 1, null, null, 1, 0, 0, null, 10, '', getdate(), '', getdate())
GO
SET IDENTITY_INSERT [dbo].[Job] OFF
GO

View File

@ -106,6 +106,7 @@ namespace Oqtane.Server
services.AddScoped<ILogService, LogService>();
services.AddScoped<IJobService, JobService>();
services.AddScoped<IJobLogService, JobLogService>();
services.AddScoped<INotificationService, NotificationService>();
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
@ -179,6 +180,7 @@ namespace Oqtane.Server
services.AddTransient<ILogManager, LogManager>();
services.AddTransient<IJobRepository, JobRepository>();
services.AddTransient<IJobLogRepository, JobLogRepository>();
services.AddTransient<INotificationRepository, NotificationRepository>();
services.AddOqtaneModules();
services.AddOqtaneThemes();
@ -326,6 +328,7 @@ namespace Oqtane.Server
services.AddTransient<ILogManager, LogManager>();
services.AddTransient<IJobRepository, JobRepository>();
services.AddTransient<IJobLogRepository, JobLogRepository>();
services.AddTransient<INotificationRepository, NotificationRepository>();
services.AddOqtaneModules();
services.AddOqtaneThemes();

View File

@ -92,3 +92,21 @@ app {
height: 1px;
background-color: gray;
}
.app-link-unstyled, .app-link-unstyled:visited, .app-link-unstyled:hover, .app-link-unstyled:active, .app-link-unstyled:focus, .app-link-unstyled:active:hover {
font-style: inherit;
color: inherit;
background-color: transparent;
font-size: inherit;
text-decoration: none;
font-variant: inherit;
font-weight: inherit;
line-height: inherit;
font-family: inherit;
border-radius: inherit;
border: inherit;
outline: inherit;
box-shadow: inherit;
padding: inherit;
vertical-align: inherit;
}

View File

@ -0,0 +1,30 @@
using System;
using System.ComponentModel.DataAnnotations.Schema;
namespace Oqtane.Models
{
public class Folder : IAuditable
{
public int FolderId { get; set; }
public int SiteId { get; set; }
public int? ParentId { get; set; }
public string Name { get; set; }
public string Path { get; set; }
public int Order { get; set; }
public string CreatedBy { get; set; }
public DateTime CreatedOn { get; set; }
public string ModifiedBy { get; set; }
public DateTime ModifiedOn { get; set; }
public string DeletedBy { get; set; }
public DateTime? DeletedOn { get; set; }
public bool IsDeleted { get; set; }
[NotMapped]
public string Permissions { get; set; }
[NotMapped]
public int Level { get; set; }
[NotMapped]
public bool HasChildren { get; set; }
}
}

View File

@ -0,0 +1,29 @@
using System;
using System.ComponentModel.DataAnnotations.Schema;
namespace Oqtane.Models
{
public class Notification : IDeletable
{
public int NotificationId { get; set; }
public int SiteId { get; set; }
public int? FromUserId { get; set; }
public int? ToUserId { get; set; }
public string ToEmail { get; set; }
public int? ParentId { get; set; }
public string Subject { get; set; }
public string Body { get; set; }
public DateTime CreatedOn { get; set; }
public bool IsDelivered { get; set; }
public DateTime? DeliveredOn { get; set; }
public string DeletedBy { get; set; }
public DateTime? DeletedOn { get; set; }
public bool IsDeleted { get; set; }
[ForeignKey("FromUserId")]
public User FromUser { get; set; }
[ForeignKey("ToUserId")]
public User ToUser { get; set; }
}
}

View File

@ -25,6 +25,8 @@
public const string PageManagementModule = "Oqtane.Modules.Admin.Pages, Oqtane.Client";
public const string ModuleMessageComponent = "Oqtane.Modules.Controls.ModuleMessage, Oqtane.Client";
public const string HostUser = "host";
public const string AllUsersRole = "All Users";
public const string HostRole = "Host Users";
public const string AdminRole = "Administrators";