diff --git a/Areas/Identity/Pages/Account/AccessDenied.cshtml b/Areas/Identity/Pages/Account/AccessDenied.cshtml new file mode 100644 index 0000000..017f6ff --- /dev/null +++ b/Areas/Identity/Pages/Account/AccessDenied.cshtml @@ -0,0 +1,10 @@ +@page +@model AccessDeniedModel +@{ + ViewData["Title"] = "Access denied"; +} + +
+

@ViewData["Title"]

+

You do not have access to this resource.

+
diff --git a/Areas/Identity/Pages/Account/AccessDenied.cshtml.cs b/Areas/Identity/Pages/Account/AccessDenied.cshtml.cs new file mode 100644 index 0000000..cb16e66 --- /dev/null +++ b/Areas/Identity/Pages/Account/AccessDenied.cshtml.cs @@ -0,0 +1,23 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +#nullable disable + +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace PSTW_CentralSystem.Areas.Identity.Pages.Account +{ + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public class AccessDeniedModel : PageModel + { + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public void OnGet() + { + } + } +} diff --git a/Areas/Identity/Pages/Account/ConfirmEmail.cshtml b/Areas/Identity/Pages/Account/ConfirmEmail.cshtml new file mode 100644 index 0000000..2deb2e5 --- /dev/null +++ b/Areas/Identity/Pages/Account/ConfirmEmail.cshtml @@ -0,0 +1,8 @@ +@page +@model ConfirmEmailModel +@{ + ViewData["Title"] = "Confirm email"; +} + +

@ViewData["Title"]

+ diff --git a/Areas/Identity/Pages/Account/ConfirmEmail.cshtml.cs b/Areas/Identity/Pages/Account/ConfirmEmail.cshtml.cs new file mode 100644 index 0000000..e294b7f --- /dev/null +++ b/Areas/Identity/Pages/Account/ConfirmEmail.cshtml.cs @@ -0,0 +1,51 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +#nullable disable + +using System; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.AspNetCore.WebUtilities; + +namespace PSTW_CentralSystem.Areas.Identity.Pages.Account +{ + public class ConfirmEmailModel : PageModel + { + private readonly UserManager _userManager; + + public ConfirmEmailModel(UserManager userManager) + { + _userManager = userManager; + } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [TempData] + public string StatusMessage { get; set; } + public async Task OnGetAsync(string userId, string code) + { + if (userId == null || code == null) + { + return RedirectToPage("/Index"); + } + + var user = await _userManager.FindByIdAsync(userId); + if (user == null) + { + return NotFound($"Unable to load user with ID '{userId}'."); + } + + code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(code)); + var result = await _userManager.ConfirmEmailAsync(user, code); + StatusMessage = result.Succeeded ? "Thank you for confirming your email." : "Error confirming your email."; + return Page(); + } + } +} diff --git a/Areas/Identity/Pages/Account/ConfirmEmailChange.cshtml b/Areas/Identity/Pages/Account/ConfirmEmailChange.cshtml new file mode 100644 index 0000000..114fa88 --- /dev/null +++ b/Areas/Identity/Pages/Account/ConfirmEmailChange.cshtml @@ -0,0 +1,8 @@ +@page +@model ConfirmEmailChangeModel +@{ + ViewData["Title"] = "Confirm email change"; +} + +

@ViewData["Title"]

+ diff --git a/Areas/Identity/Pages/Account/ConfirmEmailChange.cshtml.cs b/Areas/Identity/Pages/Account/ConfirmEmailChange.cshtml.cs new file mode 100644 index 0000000..af5ccf0 --- /dev/null +++ b/Areas/Identity/Pages/Account/ConfirmEmailChange.cshtml.cs @@ -0,0 +1,69 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +#nullable disable + +using System; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.AspNetCore.WebUtilities; + +namespace PSTW_CentralSystem.Areas.Identity.Pages.Account +{ + public class ConfirmEmailChangeModel : PageModel + { + private readonly UserManager _userManager; + private readonly SignInManager _signInManager; + + public ConfirmEmailChangeModel(UserManager userManager, SignInManager signInManager) + { + _userManager = userManager; + _signInManager = signInManager; + } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [TempData] + public string StatusMessage { get; set; } + + public async Task OnGetAsync(string userId, string email, string code) + { + if (userId == null || email == null || code == null) + { + return RedirectToPage("/Index"); + } + + var user = await _userManager.FindByIdAsync(userId); + if (user == null) + { + return NotFound($"Unable to load user with ID '{userId}'."); + } + + code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(code)); + var result = await _userManager.ChangeEmailAsync(user, email, code); + if (!result.Succeeded) + { + StatusMessage = "Error changing email."; + return Page(); + } + + // In our UI email and user name are one and the same, so when we update the email + // we need to update the user name. + var setUserNameResult = await _userManager.SetUserNameAsync(user, email); + if (!setUserNameResult.Succeeded) + { + StatusMessage = "Error changing user name."; + return Page(); + } + + await _signInManager.RefreshSignInAsync(user); + StatusMessage = "Thank you for confirming your email change."; + return Page(); + } + } +} diff --git a/Areas/Identity/Pages/Account/ExternalLogin.cshtml b/Areas/Identity/Pages/Account/ExternalLogin.cshtml new file mode 100644 index 0000000..5c99bad --- /dev/null +++ b/Areas/Identity/Pages/Account/ExternalLogin.cshtml @@ -0,0 +1,33 @@ +@page +@model ExternalLoginModel +@{ + ViewData["Title"] = "Register"; +} + +

@ViewData["Title"]

+

Associate your @Model.ProviderDisplayName account.

+
+ +

+ You've successfully authenticated with @Model.ProviderDisplayName. + Please enter an email address for this site below and click the Register button to finish + logging in. +

+ +
+
+
+ +
+ + + +
+ +
+
+
+ +@section Scripts { + +} diff --git a/Areas/Identity/Pages/Account/ExternalLogin.cshtml.cs b/Areas/Identity/Pages/Account/ExternalLogin.cshtml.cs new file mode 100644 index 0000000..acd19cf --- /dev/null +++ b/Areas/Identity/Pages/Account/ExternalLogin.cshtml.cs @@ -0,0 +1,223 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +#nullable disable + +using System; +using System.ComponentModel.DataAnnotations; +using System.Security.Claims; +using System.Text; +using System.Text.Encodings.Web; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Options; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity.UI.Services; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.AspNetCore.WebUtilities; +using Microsoft.Extensions.Logging; + +namespace PSTW_CentralSystem.Areas.Identity.Pages.Account +{ + [AllowAnonymous] + public class ExternalLoginModel : PageModel + { + private readonly SignInManager _signInManager; + private readonly UserManager _userManager; + private readonly IUserStore _userStore; + private readonly IUserEmailStore _emailStore; + private readonly IEmailSender _emailSender; + private readonly ILogger _logger; + + public ExternalLoginModel( + SignInManager signInManager, + UserManager userManager, + IUserStore userStore, + ILogger logger, + IEmailSender emailSender) + { + _signInManager = signInManager; + _userManager = userManager; + _userStore = userStore; + _emailStore = GetEmailStore(); + _logger = logger; + _emailSender = emailSender; + } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [BindProperty] + public InputModel Input { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public string ProviderDisplayName { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public string ReturnUrl { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [TempData] + public string ErrorMessage { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public class InputModel + { + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [Required] + [EmailAddress] + public string Email { get; set; } + } + + public IActionResult OnGet() => RedirectToPage("./Login"); + + public IActionResult OnPost(string provider, string returnUrl = null) + { + // Request a redirect to the external login provider. + var redirectUrl = Url.Page("./ExternalLogin", pageHandler: "Callback", values: new { returnUrl }); + var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl); + return new ChallengeResult(provider, properties); + } + + public async Task OnGetCallbackAsync(string returnUrl = null, string remoteError = null) + { + returnUrl = returnUrl ?? Url.Content("~/"); + if (remoteError != null) + { + ErrorMessage = $"Error from external provider: {remoteError}"; + return RedirectToPage("./Login", new { ReturnUrl = returnUrl }); + } + var info = await _signInManager.GetExternalLoginInfoAsync(); + if (info == null) + { + ErrorMessage = "Error loading external login information."; + return RedirectToPage("./Login", new { ReturnUrl = returnUrl }); + } + + // Sign in the user with this external login provider if the user already has a login. + var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor: true); + if (result.Succeeded) + { + _logger.LogInformation("{Name} logged in with {LoginProvider} provider.", info.Principal.Identity.Name, info.LoginProvider); + return LocalRedirect(returnUrl); + } + if (result.IsLockedOut) + { + return RedirectToPage("./Lockout"); + } + else + { + // If the user does not have an account, then ask the user to create an account. + ReturnUrl = returnUrl; + ProviderDisplayName = info.ProviderDisplayName; + if (info.Principal.HasClaim(c => c.Type == ClaimTypes.Email)) + { + Input = new InputModel + { + Email = info.Principal.FindFirstValue(ClaimTypes.Email) + }; + } + return Page(); + } + } + + public async Task OnPostConfirmationAsync(string returnUrl = null) + { + returnUrl = returnUrl ?? Url.Content("~/"); + // Get the information about the user from the external login provider + var info = await _signInManager.GetExternalLoginInfoAsync(); + if (info == null) + { + ErrorMessage = "Error loading external login information during confirmation."; + return RedirectToPage("./Login", new { ReturnUrl = returnUrl }); + } + + if (ModelState.IsValid) + { + var user = CreateUser(); + + await _userStore.SetUserNameAsync(user, Input.Email, CancellationToken.None); + await _emailStore.SetEmailAsync(user, Input.Email, CancellationToken.None); + + var result = await _userManager.CreateAsync(user); + if (result.Succeeded) + { + result = await _userManager.AddLoginAsync(user, info); + if (result.Succeeded) + { + _logger.LogInformation("User created an account using {Name} provider.", info.LoginProvider); + + var userId = await _userManager.GetUserIdAsync(user); + var code = await _userManager.GenerateEmailConfirmationTokenAsync(user); + code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code)); + var callbackUrl = Url.Page( + "/Account/ConfirmEmail", + pageHandler: null, + values: new { area = "Identity", userId = userId, code = code }, + protocol: Request.Scheme); + + await _emailSender.SendEmailAsync(Input.Email, "Confirm your email", + $"Please confirm your account by clicking here."); + + // If account confirmation is required, we need to show the link if we don't have a real email sender + if (_userManager.Options.SignIn.RequireConfirmedAccount) + { + return RedirectToPage("./RegisterConfirmation", new { Email = Input.Email }); + } + + await _signInManager.SignInAsync(user, isPersistent: false, info.LoginProvider); + return LocalRedirect(returnUrl); + } + } + foreach (var error in result.Errors) + { + ModelState.AddModelError(string.Empty, error.Description); + } + } + + ProviderDisplayName = info.ProviderDisplayName; + ReturnUrl = returnUrl; + return Page(); + } + + private IdentityUser CreateUser() + { + try + { + return Activator.CreateInstance(); + } + catch + { + throw new InvalidOperationException($"Can't create an instance of '{nameof(IdentityUser)}'. " + + $"Ensure that '{nameof(IdentityUser)}' is not an abstract class and has a parameterless constructor, or alternatively " + + $"override the external login page in /Areas/Identity/Pages/Account/ExternalLogin.cshtml"); + } + } + + private IUserEmailStore GetEmailStore() + { + if (!_userManager.SupportsUserEmail) + { + throw new NotSupportedException("The default UI requires a user store with email support."); + } + return (IUserEmailStore)_userStore; + } + } +} diff --git a/Areas/Identity/Pages/Account/ForgotPassword.cshtml b/Areas/Identity/Pages/Account/ForgotPassword.cshtml new file mode 100644 index 0000000..106b8d1 --- /dev/null +++ b/Areas/Identity/Pages/Account/ForgotPassword.cshtml @@ -0,0 +1,26 @@ +@page +@model ForgotPasswordModel +@{ + ViewData["Title"] = "Forgot your password?"; +} + +

@ViewData["Title"]

+

Enter your email.

+
+
+
+
+ +
+ + + +
+ +
+
+
+ +@section Scripts { + +} diff --git a/Areas/Identity/Pages/Account/ForgotPassword.cshtml.cs b/Areas/Identity/Pages/Account/ForgotPassword.cshtml.cs new file mode 100644 index 0000000..da82592 --- /dev/null +++ b/Areas/Identity/Pages/Account/ForgotPassword.cshtml.cs @@ -0,0 +1,84 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +#nullable disable + +using System; +using System.ComponentModel.DataAnnotations; +using System.Text; +using System.Text.Encodings.Web; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity.UI.Services; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.AspNetCore.WebUtilities; + +namespace PSTW_CentralSystem.Areas.Identity.Pages.Account +{ + public class ForgotPasswordModel : PageModel + { + private readonly UserManager _userManager; + private readonly IEmailSender _emailSender; + + public ForgotPasswordModel(UserManager userManager, IEmailSender emailSender) + { + _userManager = userManager; + _emailSender = emailSender; + } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [BindProperty] + public InputModel Input { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public class InputModel + { + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [Required] + [EmailAddress] + public string Email { get; set; } + } + + public async Task OnPostAsync() + { + if (ModelState.IsValid) + { + var user = await _userManager.FindByEmailAsync(Input.Email); + if (user == null || !(await _userManager.IsEmailConfirmedAsync(user))) + { + // Don't reveal that the user does not exist or is not confirmed + return RedirectToPage("./ForgotPasswordConfirmation"); + } + + // For more information on how to enable account confirmation and password reset please + // visit https://go.microsoft.com/fwlink/?LinkID=532713 + var code = await _userManager.GeneratePasswordResetTokenAsync(user); + code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code)); + var callbackUrl = Url.Page( + "/Account/ResetPassword", + pageHandler: null, + values: new { area = "Identity", code }, + protocol: Request.Scheme); + + await _emailSender.SendEmailAsync( + Input.Email, + "Reset Password", + $"Please reset your password by clicking here."); + + return RedirectToPage("./ForgotPasswordConfirmation"); + } + + return Page(); + } + } +} diff --git a/Areas/Identity/Pages/Account/ForgotPasswordConfirmation.cshtml b/Areas/Identity/Pages/Account/ForgotPasswordConfirmation.cshtml new file mode 100644 index 0000000..4f5ae99 --- /dev/null +++ b/Areas/Identity/Pages/Account/ForgotPasswordConfirmation.cshtml @@ -0,0 +1,10 @@ +@page +@model ForgotPasswordConfirmation +@{ + ViewData["Title"] = "Forgot password confirmation"; +} + +

@ViewData["Title"]

+

+ Please check your email to reset your password. +

diff --git a/Areas/Identity/Pages/Account/ForgotPasswordConfirmation.cshtml.cs b/Areas/Identity/Pages/Account/ForgotPasswordConfirmation.cshtml.cs new file mode 100644 index 0000000..b6c9b7f --- /dev/null +++ b/Areas/Identity/Pages/Account/ForgotPasswordConfirmation.cshtml.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +#nullable disable + +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace PSTW_CentralSystem.Areas.Identity.Pages.Account +{ + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [AllowAnonymous] + public class ForgotPasswordConfirmation : PageModel + { + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public void OnGet() + { + } + } +} diff --git a/Areas/Identity/Pages/Account/Lockout.cshtml b/Areas/Identity/Pages/Account/Lockout.cshtml new file mode 100644 index 0000000..4eded88 --- /dev/null +++ b/Areas/Identity/Pages/Account/Lockout.cshtml @@ -0,0 +1,10 @@ +@page +@model LockoutModel +@{ + ViewData["Title"] = "Locked out"; +} + +
+

@ViewData["Title"]

+

This account has been locked out, please try again later.

+
diff --git a/Areas/Identity/Pages/Account/Lockout.cshtml.cs b/Areas/Identity/Pages/Account/Lockout.cshtml.cs new file mode 100644 index 0000000..741f63b --- /dev/null +++ b/Areas/Identity/Pages/Account/Lockout.cshtml.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +#nullable disable + +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace PSTW_CentralSystem.Areas.Identity.Pages.Account +{ + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [AllowAnonymous] + public class LockoutModel : PageModel + { + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public void OnGet() + { + } + } +} diff --git a/Areas/Identity/Pages/Account/Login.cshtml b/Areas/Identity/Pages/Account/Login.cshtml new file mode 100644 index 0000000..6354164 --- /dev/null +++ b/Areas/Identity/Pages/Account/Login.cshtml @@ -0,0 +1,83 @@ +@page +@model LoginModel + +@{ + ViewData["Title"] = "Log in"; +} + +

@ViewData["Title"]

+
+
+
+
+

Use a local account to log in.

+
+ +
+ + + +
+
+ + + +
+
+ +
+
+ +
+ +
+
+
+
+
+

Use another service to log in.

+
+ @{ + if ((Model.ExternalLogins?.Count ?? 0) == 0) + { +
+

+ There are no external authentication services configured. See this article + about setting up this ASP.NET application to support logging in via external services. +

+
+ } + else + { +
+
+

+ @foreach (var provider in Model.ExternalLogins!) + { + + } +

+
+
+ } + } +
+
+
+ +@section Scripts { + +} diff --git a/Areas/Identity/Pages/Account/Login.cshtml.cs b/Areas/Identity/Pages/Account/Login.cshtml.cs new file mode 100644 index 0000000..76ef970 --- /dev/null +++ b/Areas/Identity/Pages/Account/Login.cshtml.cs @@ -0,0 +1,140 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +#nullable disable + +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity.UI.Services; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.Extensions.Logging; + +namespace PSTW_CentralSystem.Areas.Identity.Pages.Account +{ + public class LoginModel : PageModel + { + private readonly SignInManager _signInManager; + private readonly ILogger _logger; + + public LoginModel(SignInManager signInManager, ILogger logger) + { + _signInManager = signInManager; + _logger = logger; + } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [BindProperty] + public InputModel Input { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public IList ExternalLogins { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public string ReturnUrl { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [TempData] + public string ErrorMessage { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public class InputModel + { + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [Required] + [EmailAddress] + public string Email { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [Required] + [DataType(DataType.Password)] + public string Password { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [Display(Name = "Remember me?")] + public bool RememberMe { get; set; } + } + + public async Task OnGetAsync(string returnUrl = null) + { + if (!string.IsNullOrEmpty(ErrorMessage)) + { + ModelState.AddModelError(string.Empty, ErrorMessage); + } + + returnUrl ??= Url.Content("~/"); + + // Clear the existing external cookie to ensure a clean login process + await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme); + + ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList(); + + ReturnUrl = returnUrl; + } + + public async Task OnPostAsync(string returnUrl = null) + { + returnUrl ??= Url.Content("~/"); + + ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList(); + + if (ModelState.IsValid) + { + // This doesn't count login failures towards account lockout + // To enable password failures to trigger account lockout, set lockoutOnFailure: true + var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: false); + if (result.Succeeded) + { + _logger.LogInformation("User logged in."); + return LocalRedirect(returnUrl); + } + if (result.RequiresTwoFactor) + { + return RedirectToPage("./LoginWith2fa", new { ReturnUrl = returnUrl, RememberMe = Input.RememberMe }); + } + if (result.IsLockedOut) + { + _logger.LogWarning("User account locked out."); + return RedirectToPage("./Lockout"); + } + else + { + ModelState.AddModelError(string.Empty, "Invalid login attempt."); + return Page(); + } + } + + // If we got this far, something failed, redisplay form + return Page(); + } + } +} diff --git a/Areas/Identity/Pages/Account/LoginWith2fa.cshtml b/Areas/Identity/Pages/Account/LoginWith2fa.cshtml new file mode 100644 index 0000000..87d9831 --- /dev/null +++ b/Areas/Identity/Pages/Account/LoginWith2fa.cshtml @@ -0,0 +1,39 @@ +@page +@model LoginWith2faModel +@{ + ViewData["Title"] = "Two-factor authentication"; +} + +

@ViewData["Title"]

+
+

Your login is protected with an authenticator app. Enter your authenticator code below.

+
+
+
+ + +
+ + + +
+
+ +
+
+ +
+
+
+
+

+ Don't have access to your authenticator device? You can + log in with a recovery code. +

+ +@section Scripts { + +} \ No newline at end of file diff --git a/Areas/Identity/Pages/Account/LoginWith2fa.cshtml.cs b/Areas/Identity/Pages/Account/LoginWith2fa.cshtml.cs new file mode 100644 index 0000000..0e73753 --- /dev/null +++ b/Areas/Identity/Pages/Account/LoginWith2fa.cshtml.cs @@ -0,0 +1,131 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +#nullable disable + +using System; +using System.ComponentModel.DataAnnotations; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.Extensions.Logging; +using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.Logging; + +namespace PSTW_CentralSystem.Areas.Identity.Pages.Account +{ + public class LoginWith2faModel : PageModel + { + private readonly SignInManager _signInManager; + private readonly UserManager _userManager; + private readonly ILogger _logger; + + public LoginWith2faModel( + SignInManager signInManager, + UserManager userManager, + ILogger logger) + { + _signInManager = signInManager; + _userManager = userManager; + _logger = logger; + } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [BindProperty] + public InputModel Input { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public bool RememberMe { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public string ReturnUrl { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public class InputModel + { + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [Required] + [StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] + [DataType(DataType.Text)] + [Display(Name = "Authenticator code")] + public string TwoFactorCode { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [Display(Name = "Remember this machine")] + public bool RememberMachine { get; set; } + } + + public async Task OnGetAsync(bool rememberMe, string returnUrl = null) + { + // Ensure the user has gone through the username & password screen first + var user = await _signInManager.GetTwoFactorAuthenticationUserAsync(); + + if (user == null) + { + throw new InvalidOperationException($"Unable to load two-factor authentication user."); + } + + ReturnUrl = returnUrl; + RememberMe = rememberMe; + + return Page(); + } + + public async Task OnPostAsync(bool rememberMe, string returnUrl = null) + { + if (!ModelState.IsValid) + { + return Page(); + } + + returnUrl = returnUrl ?? Url.Content("~/"); + + var user = await _signInManager.GetTwoFactorAuthenticationUserAsync(); + if (user == null) + { + throw new InvalidOperationException($"Unable to load two-factor authentication user."); + } + + var authenticatorCode = Input.TwoFactorCode.Replace(" ", string.Empty).Replace("-", string.Empty); + + var result = await _signInManager.TwoFactorAuthenticatorSignInAsync(authenticatorCode, rememberMe, Input.RememberMachine); + + var userId = await _userManager.GetUserIdAsync(user); + + if (result.Succeeded) + { + _logger.LogInformation("User with ID '{UserId}' logged in with 2fa.", user.Id); + return LocalRedirect(returnUrl); + } + else if (result.IsLockedOut) + { + _logger.LogWarning("User with ID '{UserId}' account locked out.", user.Id); + return RedirectToPage("./Lockout"); + } + else + { + _logger.LogWarning("Invalid authenticator code entered for user with ID '{UserId}'.", user.Id); + ModelState.AddModelError(string.Empty, "Invalid authenticator code."); + return Page(); + } + } + } +} diff --git a/Areas/Identity/Pages/Account/LoginWithRecoveryCode.cshtml b/Areas/Identity/Pages/Account/LoginWithRecoveryCode.cshtml new file mode 100644 index 0000000..0d44e37 --- /dev/null +++ b/Areas/Identity/Pages/Account/LoginWithRecoveryCode.cshtml @@ -0,0 +1,29 @@ +@page +@model LoginWithRecoveryCodeModel +@{ + ViewData["Title"] = "Recovery code verification"; +} + +

@ViewData["Title"]

+
+

+ You have requested to log in with a recovery code. This login will not be remembered until you provide + an authenticator app code at log in or disable 2FA and log in again. +

+
+
+
+ +
+ + + +
+ +
+
+
+ +@section Scripts { + +} \ No newline at end of file diff --git a/Areas/Identity/Pages/Account/LoginWithRecoveryCode.cshtml.cs b/Areas/Identity/Pages/Account/LoginWithRecoveryCode.cshtml.cs new file mode 100644 index 0000000..53b8ee6 --- /dev/null +++ b/Areas/Identity/Pages/Account/LoginWithRecoveryCode.cshtml.cs @@ -0,0 +1,112 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +#nullable disable + +using System; +using System.ComponentModel.DataAnnotations; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.Extensions.Logging; +namespace PSTW_CentralSystem.Areas.Identity.Pages.Account +{ + public class LoginWithRecoveryCodeModel : PageModel + { + private readonly SignInManager _signInManager; + private readonly UserManager _userManager; + private readonly ILogger _logger; + + public LoginWithRecoveryCodeModel( + SignInManager signInManager, + UserManager userManager, + ILogger logger) + { + _signInManager = signInManager; + _userManager = userManager; + _logger = logger; + } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [BindProperty] + public InputModel Input { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public string ReturnUrl { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public class InputModel + { + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [BindProperty] + [Required] + [DataType(DataType.Text)] + [Display(Name = "Recovery Code")] + public string RecoveryCode { get; set; } + } + + public async Task OnGetAsync(string returnUrl = null) + { + // Ensure the user has gone through the username & password screen first + var user = await _signInManager.GetTwoFactorAuthenticationUserAsync(); + if (user == null) + { + throw new InvalidOperationException($"Unable to load two-factor authentication user."); + } + + ReturnUrl = returnUrl; + + return Page(); + } + + public async Task OnPostAsync(string returnUrl = null) + { + if (!ModelState.IsValid) + { + return Page(); + } + + var user = await _signInManager.GetTwoFactorAuthenticationUserAsync(); + if (user == null) + { + throw new InvalidOperationException($"Unable to load two-factor authentication user."); + } + + var recoveryCode = Input.RecoveryCode.Replace(" ", string.Empty); + + var result = await _signInManager.TwoFactorRecoveryCodeSignInAsync(recoveryCode); + + var userId = await _userManager.GetUserIdAsync(user); + + if (result.Succeeded) + { + _logger.LogInformation("User with ID '{UserId}' logged in with a recovery code.", user.Id); + return LocalRedirect(returnUrl ?? Url.Content("~/")); + } + if (result.IsLockedOut) + { + _logger.LogWarning("User account locked out."); + return RedirectToPage("./Lockout"); + } + else + { + _logger.LogWarning("Invalid recovery code entered for user with ID '{UserId}' ", user.Id); + ModelState.AddModelError(string.Empty, "Invalid recovery code entered."); + return Page(); + } + } + } +} diff --git a/Areas/Identity/Pages/Account/Logout.cshtml b/Areas/Identity/Pages/Account/Logout.cshtml new file mode 100644 index 0000000..cad49d7 --- /dev/null +++ b/Areas/Identity/Pages/Account/Logout.cshtml @@ -0,0 +1,21 @@ +@page +@model LogoutModel +@{ + ViewData["Title"] = "Log out"; +} + +
+

@ViewData["Title"]

+ @{ + if (User.Identity?.IsAuthenticated ?? false) + { +
+ +
+ } + else + { +

You have successfully logged out of the application.

+ } + } +
diff --git a/Areas/Identity/Pages/Account/Logout.cshtml.cs b/Areas/Identity/Pages/Account/Logout.cshtml.cs new file mode 100644 index 0000000..75e87c5 --- /dev/null +++ b/Areas/Identity/Pages/Account/Logout.cshtml.cs @@ -0,0 +1,42 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +#nullable disable + +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.Extensions.Logging; + +namespace PSTW_CentralSystem.Areas.Identity.Pages.Account +{ + public class LogoutModel : PageModel + { + private readonly SignInManager _signInManager; + private readonly ILogger _logger; + + public LogoutModel(SignInManager signInManager, ILogger logger) + { + _signInManager = signInManager; + _logger = logger; + } + + public async Task OnPost(string returnUrl = null) + { + await _signInManager.SignOutAsync(); + _logger.LogInformation("User logged out."); + if (returnUrl != null) + { + return LocalRedirect(returnUrl); + } + else + { + // This needs to be a redirect so that the browser performs a new + // request and the identity for the user gets updated. + return RedirectToPage(); + } + } + } +} diff --git a/Areas/Identity/Pages/Account/Manage/ChangePassword.cshtml b/Areas/Identity/Pages/Account/Manage/ChangePassword.cshtml new file mode 100644 index 0000000..073603a --- /dev/null +++ b/Areas/Identity/Pages/Account/Manage/ChangePassword.cshtml @@ -0,0 +1,36 @@ +@page +@model ChangePasswordModel +@{ + ViewData["Title"] = "Change password"; + ViewData["ActivePage"] = ManageNavPages.ChangePassword; +} + +

@ViewData["Title"]

+ +
+
+
+ +
+ + + +
+
+ + + +
+
+ + + +
+ +
+
+
+ +@section Scripts { + +} diff --git a/Areas/Identity/Pages/Account/Manage/ChangePassword.cshtml.cs b/Areas/Identity/Pages/Account/Manage/ChangePassword.cshtml.cs new file mode 100644 index 0000000..f076826 --- /dev/null +++ b/Areas/Identity/Pages/Account/Manage/ChangePassword.cshtml.cs @@ -0,0 +1,127 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +#nullable disable + +using System; +using System.ComponentModel.DataAnnotations; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.Extensions.Logging; + +namespace PSTW_CentralSystem.Areas.Identity.Pages.Account.Manage +{ + public class ChangePasswordModel : PageModel + { + private readonly UserManager _userManager; + private readonly SignInManager _signInManager; + private readonly ILogger _logger; + + public ChangePasswordModel( + UserManager userManager, + SignInManager signInManager, + ILogger logger) + { + _userManager = userManager; + _signInManager = signInManager; + _logger = logger; + } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [BindProperty] + public InputModel Input { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [TempData] + public string StatusMessage { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public class InputModel + { + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [Required] + [DataType(DataType.Password)] + [Display(Name = "Current password")] + public string OldPassword { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [Required] + [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] + [DataType(DataType.Password)] + [Display(Name = "New password")] + public string NewPassword { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [DataType(DataType.Password)] + [Display(Name = "Confirm new password")] + [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")] + public string ConfirmPassword { get; set; } + } + + public async Task OnGetAsync() + { + var user = await _userManager.GetUserAsync(User); + if (user == null) + { + return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); + } + + var hasPassword = await _userManager.HasPasswordAsync(user); + if (!hasPassword) + { + return RedirectToPage("./SetPassword"); + } + + return Page(); + } + + public async Task OnPostAsync() + { + if (!ModelState.IsValid) + { + return Page(); + } + + var user = await _userManager.GetUserAsync(User); + if (user == null) + { + return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); + } + + var changePasswordResult = await _userManager.ChangePasswordAsync(user, Input.OldPassword, Input.NewPassword); + if (!changePasswordResult.Succeeded) + { + foreach (var error in changePasswordResult.Errors) + { + ModelState.AddModelError(string.Empty, error.Description); + } + return Page(); + } + + await _signInManager.RefreshSignInAsync(user); + _logger.LogInformation("User changed their password successfully."); + StatusMessage = "Your password has been changed."; + + return RedirectToPage(); + } + } +} diff --git a/Areas/Identity/Pages/Account/Manage/DeletePersonalData.cshtml b/Areas/Identity/Pages/Account/Manage/DeletePersonalData.cshtml new file mode 100644 index 0000000..cecdd1d --- /dev/null +++ b/Areas/Identity/Pages/Account/Manage/DeletePersonalData.cshtml @@ -0,0 +1,33 @@ +@page +@model DeletePersonalDataModel +@{ + ViewData["Title"] = "Delete Personal Data"; + ViewData["ActivePage"] = ManageNavPages.PersonalData; +} + +

@ViewData["Title"]

+ + + +
+
+ + @if (Model.RequirePassword) + { +
+ + + +
+ } + +
+
+ +@section Scripts { + +} diff --git a/Areas/Identity/Pages/Account/Manage/DeletePersonalData.cshtml.cs b/Areas/Identity/Pages/Account/Manage/DeletePersonalData.cshtml.cs new file mode 100644 index 0000000..6181d0a --- /dev/null +++ b/Areas/Identity/Pages/Account/Manage/DeletePersonalData.cshtml.cs @@ -0,0 +1,103 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +#nullable disable + +using System; +using System.ComponentModel.DataAnnotations; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.Extensions.Logging; + +namespace PSTW_CentralSystem.Areas.Identity.Pages.Account.Manage +{ + public class DeletePersonalDataModel : PageModel + { + private readonly UserManager _userManager; + private readonly SignInManager _signInManager; + private readonly ILogger _logger; + + public DeletePersonalDataModel( + UserManager userManager, + SignInManager signInManager, + ILogger logger) + { + _userManager = userManager; + _signInManager = signInManager; + _logger = logger; + } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [BindProperty] + public InputModel Input { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public class InputModel + { + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [Required] + [DataType(DataType.Password)] + public string Password { get; set; } + } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public bool RequirePassword { get; set; } + + public async Task OnGet() + { + var user = await _userManager.GetUserAsync(User); + if (user == null) + { + return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); + } + + RequirePassword = await _userManager.HasPasswordAsync(user); + return Page(); + } + + public async Task OnPostAsync() + { + var user = await _userManager.GetUserAsync(User); + if (user == null) + { + return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); + } + + RequirePassword = await _userManager.HasPasswordAsync(user); + if (RequirePassword) + { + if (!await _userManager.CheckPasswordAsync(user, Input.Password)) + { + ModelState.AddModelError(string.Empty, "Incorrect password."); + return Page(); + } + } + + var result = await _userManager.DeleteAsync(user); + var userId = await _userManager.GetUserIdAsync(user); + if (!result.Succeeded) + { + throw new InvalidOperationException($"Unexpected error occurred deleting user."); + } + + await _signInManager.SignOutAsync(); + + _logger.LogInformation("User with ID '{UserId}' deleted themselves.", userId); + + return Redirect("~/"); + } + } +} diff --git a/Areas/Identity/Pages/Account/Manage/Disable2fa.cshtml b/Areas/Identity/Pages/Account/Manage/Disable2fa.cshtml new file mode 100644 index 0000000..31ecb7e --- /dev/null +++ b/Areas/Identity/Pages/Account/Manage/Disable2fa.cshtml @@ -0,0 +1,25 @@ +@page +@model Disable2faModel +@{ + ViewData["Title"] = "Disable two-factor authentication (2FA)"; + ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication; +} + + +

@ViewData["Title"]

+ + + +
+
+ +
+
diff --git a/Areas/Identity/Pages/Account/Manage/Disable2fa.cshtml.cs b/Areas/Identity/Pages/Account/Manage/Disable2fa.cshtml.cs new file mode 100644 index 0000000..4b682b0 --- /dev/null +++ b/Areas/Identity/Pages/Account/Manage/Disable2fa.cshtml.cs @@ -0,0 +1,69 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +#nullable disable + +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.Extensions.Logging; + +namespace PSTW_CentralSystem.Areas.Identity.Pages.Account.Manage +{ + public class Disable2faModel : PageModel + { + private readonly UserManager _userManager; + private readonly ILogger _logger; + + public Disable2faModel( + UserManager userManager, + ILogger logger) + { + _userManager = userManager; + _logger = logger; + } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [TempData] + public string StatusMessage { get; set; } + + public async Task OnGet() + { + var user = await _userManager.GetUserAsync(User); + if (user == null) + { + return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); + } + + if (!await _userManager.GetTwoFactorEnabledAsync(user)) + { + throw new InvalidOperationException($"Cannot disable 2FA for user as it's not currently enabled."); + } + + return Page(); + } + + public async Task OnPostAsync() + { + var user = await _userManager.GetUserAsync(User); + if (user == null) + { + return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); + } + + var disable2faResult = await _userManager.SetTwoFactorEnabledAsync(user, false); + if (!disable2faResult.Succeeded) + { + throw new InvalidOperationException($"Unexpected error occurred disabling 2FA."); + } + + _logger.LogInformation("User with ID '{UserId}' has disabled 2fa.", _userManager.GetUserId(User)); + StatusMessage = "2fa has been disabled. You can reenable 2fa when you setup an authenticator app"; + return RedirectToPage("./TwoFactorAuthentication"); + } + } +} diff --git a/Areas/Identity/Pages/Account/Manage/DownloadPersonalData.cshtml b/Areas/Identity/Pages/Account/Manage/DownloadPersonalData.cshtml new file mode 100644 index 0000000..93f631f --- /dev/null +++ b/Areas/Identity/Pages/Account/Manage/DownloadPersonalData.cshtml @@ -0,0 +1,12 @@ +@page +@model DownloadPersonalDataModel +@{ + ViewData["Title"] = "Download Your Data"; + ViewData["ActivePage"] = ManageNavPages.PersonalData; +} + +

@ViewData["Title"]

+ +@section Scripts { + +} diff --git a/Areas/Identity/Pages/Account/Manage/DownloadPersonalData.cshtml.cs b/Areas/Identity/Pages/Account/Manage/DownloadPersonalData.cshtml.cs new file mode 100644 index 0000000..3ae8471 --- /dev/null +++ b/Areas/Identity/Pages/Account/Manage/DownloadPersonalData.cshtml.cs @@ -0,0 +1,67 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +#nullable disable + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.Extensions.Logging; + +namespace PSTW_CentralSystem.Areas.Identity.Pages.Account.Manage +{ + public class DownloadPersonalDataModel : PageModel + { + private readonly UserManager _userManager; + private readonly ILogger _logger; + + public DownloadPersonalDataModel( + UserManager userManager, + ILogger logger) + { + _userManager = userManager; + _logger = logger; + } + + public IActionResult OnGet() + { + return NotFound(); + } + + public async Task OnPostAsync() + { + var user = await _userManager.GetUserAsync(User); + if (user == null) + { + return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); + } + + _logger.LogInformation("User with ID '{UserId}' asked for their personal data.", _userManager.GetUserId(User)); + + // Only include personal data for download + var personalData = new Dictionary(); + var personalDataProps = typeof(IdentityUser).GetProperties().Where( + prop => Attribute.IsDefined(prop, typeof(PersonalDataAttribute))); + foreach (var p in personalDataProps) + { + personalData.Add(p.Name, p.GetValue(user)?.ToString() ?? "null"); + } + + var logins = await _userManager.GetLoginsAsync(user); + foreach (var l in logins) + { + personalData.Add($"{l.LoginProvider} external login provider key", l.ProviderKey); + } + + personalData.Add($"Authenticator Key", await _userManager.GetAuthenticatorKeyAsync(user)); + + Response.Headers.TryAdd("Content-Disposition", "attachment; filename=PersonalData.json"); + return new FileContentResult(JsonSerializer.SerializeToUtf8Bytes(personalData), "application/json"); + } + } +} diff --git a/Areas/Identity/Pages/Account/Manage/Email.cshtml b/Areas/Identity/Pages/Account/Manage/Email.cshtml new file mode 100644 index 0000000..f3278e9 --- /dev/null +++ b/Areas/Identity/Pages/Account/Manage/Email.cshtml @@ -0,0 +1,44 @@ +@page +@model EmailModel +@{ + ViewData["Title"] = "Manage Email"; + ViewData["ActivePage"] = ManageNavPages.Email; +} + +

@ViewData["Title"]

+ +
+
+
+ + @if (Model.IsEmailConfirmed) + { +
+ +
+ +
+ +
+ } + else + { +
+ + + +
+ } +
+ + + +
+ +
+
+
+ +@section Scripts { + +} diff --git a/Areas/Identity/Pages/Account/Manage/Email.cshtml.cs b/Areas/Identity/Pages/Account/Manage/Email.cshtml.cs new file mode 100644 index 0000000..dcdd1b9 --- /dev/null +++ b/Areas/Identity/Pages/Account/Manage/Email.cshtml.cs @@ -0,0 +1,171 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +#nullable disable + +using System; +using System.ComponentModel.DataAnnotations; +using System.Text; +using System.Text.Encodings.Web; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity.UI.Services; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.AspNetCore.WebUtilities; + +namespace PSTW_CentralSystem.Areas.Identity.Pages.Account.Manage +{ + public class EmailModel : PageModel + { + private readonly UserManager _userManager; + private readonly SignInManager _signInManager; + private readonly IEmailSender _emailSender; + + public EmailModel( + UserManager userManager, + SignInManager signInManager, + IEmailSender emailSender) + { + _userManager = userManager; + _signInManager = signInManager; + _emailSender = emailSender; + } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public string Email { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public bool IsEmailConfirmed { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [TempData] + public string StatusMessage { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [BindProperty] + public InputModel Input { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public class InputModel + { + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [Required] + [EmailAddress] + [Display(Name = "New email")] + public string NewEmail { get; set; } + } + + private async Task LoadAsync(IdentityUser user) + { + var email = await _userManager.GetEmailAsync(user); + Email = email; + + Input = new InputModel + { + NewEmail = email, + }; + + IsEmailConfirmed = await _userManager.IsEmailConfirmedAsync(user); + } + + public async Task OnGetAsync() + { + var user = await _userManager.GetUserAsync(User); + if (user == null) + { + return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); + } + + await LoadAsync(user); + return Page(); + } + + public async Task OnPostChangeEmailAsync() + { + var user = await _userManager.GetUserAsync(User); + if (user == null) + { + return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); + } + + if (!ModelState.IsValid) + { + await LoadAsync(user); + return Page(); + } + + var email = await _userManager.GetEmailAsync(user); + if (Input.NewEmail != email) + { + var userId = await _userManager.GetUserIdAsync(user); + var code = await _userManager.GenerateChangeEmailTokenAsync(user, Input.NewEmail); + code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code)); + var callbackUrl = Url.Page( + "/Account/ConfirmEmailChange", + pageHandler: null, + values: new { area = "Identity", userId = userId, email = Input.NewEmail, code = code }, + protocol: Request.Scheme); + await _emailSender.SendEmailAsync( + Input.NewEmail, + "Confirm your email", + $"Please confirm your account by clicking here."); + + StatusMessage = "Confirmation link to change email sent. Please check your email."; + return RedirectToPage(); + } + + StatusMessage = "Your email is unchanged."; + return RedirectToPage(); + } + + public async Task OnPostSendVerificationEmailAsync() + { + var user = await _userManager.GetUserAsync(User); + if (user == null) + { + return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); + } + + if (!ModelState.IsValid) + { + await LoadAsync(user); + return Page(); + } + + var userId = await _userManager.GetUserIdAsync(user); + var email = await _userManager.GetEmailAsync(user); + var code = await _userManager.GenerateEmailConfirmationTokenAsync(user); + code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code)); + var callbackUrl = Url.Page( + "/Account/ConfirmEmail", + pageHandler: null, + values: new { area = "Identity", userId = userId, code = code }, + protocol: Request.Scheme); + await _emailSender.SendEmailAsync( + email, + "Confirm your email", + $"Please confirm your account by clicking here."); + + StatusMessage = "Verification email sent. Please check your email."; + return RedirectToPage(); + } + } +} diff --git a/Areas/Identity/Pages/Account/Manage/EnableAuthenticator.cshtml b/Areas/Identity/Pages/Account/Manage/EnableAuthenticator.cshtml new file mode 100644 index 0000000..fefa57f --- /dev/null +++ b/Areas/Identity/Pages/Account/Manage/EnableAuthenticator.cshtml @@ -0,0 +1,53 @@ +@page +@model EnableAuthenticatorModel +@{ + ViewData["Title"] = "Configure authenticator app"; + ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication; +} + + +

@ViewData["Title"]

+
+

To use an authenticator app go through the following steps:

+
    +
  1. +

    + Download a two-factor authenticator app like Microsoft Authenticator for + Android and + iOS or + Google Authenticator for + Android and + iOS. +

    +
  2. +
  3. +

    Scan the QR Code or enter this key @Model.SharedKey into your two factor authenticator app. Spaces and casing do not matter.

    + +
    +
    +
  4. +
  5. +

    + Once you have scanned the QR code or input the key above, your two factor authentication app will provide you + with a unique code. Enter the code in the confirmation box below. +

    +
    +
    +
    +
    + + + +
    + + +
    +
    +
    +
  6. +
+
+ +@section Scripts { + +} diff --git a/Areas/Identity/Pages/Account/Manage/EnableAuthenticator.cshtml.cs b/Areas/Identity/Pages/Account/Manage/EnableAuthenticator.cshtml.cs new file mode 100644 index 0000000..4d010b5 --- /dev/null +++ b/Areas/Identity/Pages/Account/Manage/EnableAuthenticator.cshtml.cs @@ -0,0 +1,188 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +#nullable disable + +using System; +using System.ComponentModel.DataAnnotations; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Text.Encodings.Web; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.Extensions.Logging; + +namespace PSTW_CentralSystem.Areas.Identity.Pages.Account.Manage +{ + public class EnableAuthenticatorModel : PageModel + { + private readonly UserManager _userManager; + private readonly ILogger _logger; + private readonly UrlEncoder _urlEncoder; + + private const string AuthenticatorUriFormat = "otpauth://totp/{0}:{1}?secret={2}&issuer={0}&digits=6"; + + public EnableAuthenticatorModel( + UserManager userManager, + ILogger logger, + UrlEncoder urlEncoder) + { + _userManager = userManager; + _logger = logger; + _urlEncoder = urlEncoder; + } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public string SharedKey { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public string AuthenticatorUri { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [TempData] + public string[] RecoveryCodes { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [TempData] + public string StatusMessage { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [BindProperty] + public InputModel Input { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public class InputModel + { + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [Required] + [StringLength(7, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] + [DataType(DataType.Text)] + [Display(Name = "Verification Code")] + public string Code { get; set; } + } + + public async Task OnGetAsync() + { + var user = await _userManager.GetUserAsync(User); + if (user == null) + { + return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); + } + + await LoadSharedKeyAndQrCodeUriAsync(user); + + return Page(); + } + + public async Task OnPostAsync() + { + var user = await _userManager.GetUserAsync(User); + if (user == null) + { + return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); + } + + if (!ModelState.IsValid) + { + await LoadSharedKeyAndQrCodeUriAsync(user); + return Page(); + } + + // Strip spaces and hyphens + var verificationCode = Input.Code.Replace(" ", string.Empty).Replace("-", string.Empty); + + var is2faTokenValid = await _userManager.VerifyTwoFactorTokenAsync( + user, _userManager.Options.Tokens.AuthenticatorTokenProvider, verificationCode); + + if (!is2faTokenValid) + { + ModelState.AddModelError("Input.Code", "Verification code is invalid."); + await LoadSharedKeyAndQrCodeUriAsync(user); + return Page(); + } + + await _userManager.SetTwoFactorEnabledAsync(user, true); + var userId = await _userManager.GetUserIdAsync(user); + _logger.LogInformation("User with ID '{UserId}' has enabled 2FA with an authenticator app.", userId); + + StatusMessage = "Your authenticator app has been verified."; + + if (await _userManager.CountRecoveryCodesAsync(user) == 0) + { + var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10); + RecoveryCodes = recoveryCodes.ToArray(); + return RedirectToPage("./ShowRecoveryCodes"); + } + else + { + return RedirectToPage("./TwoFactorAuthentication"); + } + } + + private async Task LoadSharedKeyAndQrCodeUriAsync(IdentityUser user) + { + // Load the authenticator key & QR code URI to display on the form + var unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user); + if (string.IsNullOrEmpty(unformattedKey)) + { + await _userManager.ResetAuthenticatorKeyAsync(user); + unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user); + } + + SharedKey = FormatKey(unformattedKey); + + var email = await _userManager.GetEmailAsync(user); + AuthenticatorUri = GenerateQrCodeUri(email, unformattedKey); + } + + private string FormatKey(string unformattedKey) + { + var result = new StringBuilder(); + int currentPosition = 0; + while (currentPosition + 4 < unformattedKey.Length) + { + result.Append(unformattedKey.AsSpan(currentPosition, 4)).Append(' '); + currentPosition += 4; + } + if (currentPosition < unformattedKey.Length) + { + result.Append(unformattedKey.AsSpan(currentPosition)); + } + + return result.ToString().ToLowerInvariant(); + } + + private string GenerateQrCodeUri(string email, string unformattedKey) + { + return string.Format( + CultureInfo.InvariantCulture, + AuthenticatorUriFormat, + _urlEncoder.Encode("Microsoft.AspNetCore.Identity.UI"), + _urlEncoder.Encode(email), + unformattedKey); + } + } +} diff --git a/Areas/Identity/Pages/Account/Manage/ExternalLogins.cshtml b/Areas/Identity/Pages/Account/Manage/ExternalLogins.cshtml new file mode 100644 index 0000000..7c397e5 --- /dev/null +++ b/Areas/Identity/Pages/Account/Manage/ExternalLogins.cshtml @@ -0,0 +1,53 @@ +@page +@model ExternalLoginsModel +@{ + ViewData["Title"] = "Manage your external logins"; + ViewData["ActivePage"] = ManageNavPages.ExternalLogins; +} + + +@if (Model.CurrentLogins?.Count > 0) +{ +

Registered Logins

+ + + @foreach (var login in Model.CurrentLogins) + { + + + + + } + +
@login.ProviderDisplayName + @if (Model.ShowRemoveButton) + { +
+
+ + + +
+
+ } + else + { + @:   + } +
+} +@if (Model.OtherLogins?.Count > 0) +{ +

Add another service to log in.

+
+ +} diff --git a/Areas/Identity/Pages/Account/Manage/ExternalLogins.cshtml.cs b/Areas/Identity/Pages/Account/Manage/ExternalLogins.cshtml.cs new file mode 100644 index 0000000..535418e --- /dev/null +++ b/Areas/Identity/Pages/Account/Manage/ExternalLogins.cshtml.cs @@ -0,0 +1,141 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +#nullable disable + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace PSTW_CentralSystem.Areas.Identity.Pages.Account.Manage +{ + public class ExternalLoginsModel : PageModel + { + private readonly UserManager _userManager; + private readonly SignInManager _signInManager; + private readonly IUserStore _userStore; + + public ExternalLoginsModel( + UserManager userManager, + SignInManager signInManager, + IUserStore userStore) + { + _userManager = userManager; + _signInManager = signInManager; + _userStore = userStore; + } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public IList CurrentLogins { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public IList OtherLogins { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public bool ShowRemoveButton { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [TempData] + public string StatusMessage { get; set; } + + public async Task OnGetAsync() + { + var user = await _userManager.GetUserAsync(User); + if (user == null) + { + return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); + } + + CurrentLogins = await _userManager.GetLoginsAsync(user); + OtherLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()) + .Where(auth => CurrentLogins.All(ul => auth.Name != ul.LoginProvider)) + .ToList(); + + string passwordHash = null; + if (_userStore is IUserPasswordStore userPasswordStore) + { + passwordHash = await userPasswordStore.GetPasswordHashAsync(user, HttpContext.RequestAborted); + } + + ShowRemoveButton = passwordHash != null || CurrentLogins.Count > 1; + return Page(); + } + + public async Task OnPostRemoveLoginAsync(string loginProvider, string providerKey) + { + var user = await _userManager.GetUserAsync(User); + if (user == null) + { + return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); + } + + var result = await _userManager.RemoveLoginAsync(user, loginProvider, providerKey); + if (!result.Succeeded) + { + StatusMessage = "The external login was not removed."; + return RedirectToPage(); + } + + await _signInManager.RefreshSignInAsync(user); + StatusMessage = "The external login was removed."; + return RedirectToPage(); + } + + public async Task OnPostLinkLoginAsync(string provider) + { + // Clear the existing external cookie to ensure a clean login process + await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme); + + // Request a redirect to the external login provider to link a login for the current user + var redirectUrl = Url.Page("./ExternalLogins", pageHandler: "LinkLoginCallback"); + var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl, _userManager.GetUserId(User)); + return new ChallengeResult(provider, properties); + } + + public async Task OnGetLinkLoginCallbackAsync() + { + var user = await _userManager.GetUserAsync(User); + if (user == null) + { + return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); + } + + var userId = await _userManager.GetUserIdAsync(user); + var info = await _signInManager.GetExternalLoginInfoAsync(userId); + if (info == null) + { + throw new InvalidOperationException($"Unexpected error occurred loading external login info."); + } + + var result = await _userManager.AddLoginAsync(user, info); + if (!result.Succeeded) + { + StatusMessage = "The external login was not added. External logins can only be associated with one account."; + return RedirectToPage(); + } + + // Clear the existing external cookie to ensure a clean login process + await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme); + + StatusMessage = "The external login was added."; + return RedirectToPage(); + } + } +} diff --git a/Areas/Identity/Pages/Account/Manage/GenerateRecoveryCodes.cshtml b/Areas/Identity/Pages/Account/Manage/GenerateRecoveryCodes.cshtml new file mode 100644 index 0000000..91e1d01 --- /dev/null +++ b/Areas/Identity/Pages/Account/Manage/GenerateRecoveryCodes.cshtml @@ -0,0 +1,27 @@ +@page +@model GenerateRecoveryCodesModel +@{ + ViewData["Title"] = "Generate two-factor authentication (2FA) recovery codes"; + ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication; +} + + +

@ViewData["Title"]

+ +
+
+ +
+
diff --git a/Areas/Identity/Pages/Account/Manage/GenerateRecoveryCodes.cshtml.cs b/Areas/Identity/Pages/Account/Manage/GenerateRecoveryCodes.cshtml.cs new file mode 100644 index 0000000..588965e --- /dev/null +++ b/Areas/Identity/Pages/Account/Manage/GenerateRecoveryCodes.cshtml.cs @@ -0,0 +1,82 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +#nullable disable + +using System; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.Extensions.Logging; + +namespace PSTW_CentralSystem.Areas.Identity.Pages.Account.Manage +{ + public class GenerateRecoveryCodesModel : PageModel + { + private readonly UserManager _userManager; + private readonly ILogger _logger; + + public GenerateRecoveryCodesModel( + UserManager userManager, + ILogger logger) + { + _userManager = userManager; + _logger = logger; + } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [TempData] + public string[] RecoveryCodes { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [TempData] + public string StatusMessage { get; set; } + + public async Task OnGetAsync() + { + var user = await _userManager.GetUserAsync(User); + if (user == null) + { + return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); + } + + var isTwoFactorEnabled = await _userManager.GetTwoFactorEnabledAsync(user); + if (!isTwoFactorEnabled) + { + throw new InvalidOperationException($"Cannot generate recovery codes for user because they do not have 2FA enabled."); + } + + return Page(); + } + + public async Task OnPostAsync() + { + var user = await _userManager.GetUserAsync(User); + if (user == null) + { + return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); + } + + var isTwoFactorEnabled = await _userManager.GetTwoFactorEnabledAsync(user); + var userId = await _userManager.GetUserIdAsync(user); + if (!isTwoFactorEnabled) + { + throw new InvalidOperationException($"Cannot generate recovery codes for user as they do not have 2FA enabled."); + } + + var recoveryCodes = await _userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10); + RecoveryCodes = recoveryCodes.ToArray(); + + _logger.LogInformation("User with ID '{UserId}' has generated new 2FA recovery codes.", userId); + StatusMessage = "You have generated new recovery codes."; + return RedirectToPage("./ShowRecoveryCodes"); + } + } +} diff --git a/Areas/Identity/Pages/Account/Manage/Index.cshtml b/Areas/Identity/Pages/Account/Manage/Index.cshtml new file mode 100644 index 0000000..94b669d --- /dev/null +++ b/Areas/Identity/Pages/Account/Manage/Index.cshtml @@ -0,0 +1,30 @@ +@page +@model IndexModel +@{ + ViewData["Title"] = "Profile"; + ViewData["ActivePage"] = ManageNavPages.Index; +} + +

@ViewData["Title"]

+ +
+
+
+ +
+ + +
+
+ + + +
+ +
+
+
+ +@section Scripts { + +} diff --git a/Areas/Identity/Pages/Account/Manage/Index.cshtml.cs b/Areas/Identity/Pages/Account/Manage/Index.cshtml.cs new file mode 100644 index 0000000..2a00afc --- /dev/null +++ b/Areas/Identity/Pages/Account/Manage/Index.cshtml.cs @@ -0,0 +1,118 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +#nullable disable + +using System; +using System.ComponentModel.DataAnnotations; +using System.Text.Encodings.Web; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace PSTW_CentralSystem.Areas.Identity.Pages.Account.Manage +{ + public class IndexModel : PageModel + { + private readonly UserManager _userManager; + private readonly SignInManager _signInManager; + + public IndexModel( + UserManager userManager, + SignInManager signInManager) + { + _userManager = userManager; + _signInManager = signInManager; + } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public string Username { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [TempData] + public string StatusMessage { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [BindProperty] + public InputModel Input { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public class InputModel + { + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [Phone] + [Display(Name = "Phone number")] + public string PhoneNumber { get; set; } + } + + private async Task LoadAsync(IdentityUser user) + { + var userName = await _userManager.GetUserNameAsync(user); + var phoneNumber = await _userManager.GetPhoneNumberAsync(user); + + Username = userName; + + Input = new InputModel + { + PhoneNumber = phoneNumber + }; + } + + public async Task OnGetAsync() + { + var user = await _userManager.GetUserAsync(User); + if (user == null) + { + return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); + } + + await LoadAsync(user); + return Page(); + } + + public async Task OnPostAsync() + { + var user = await _userManager.GetUserAsync(User); + if (user == null) + { + return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); + } + + if (!ModelState.IsValid) + { + await LoadAsync(user); + return Page(); + } + + var phoneNumber = await _userManager.GetPhoneNumberAsync(user); + if (Input.PhoneNumber != phoneNumber) + { + var setPhoneResult = await _userManager.SetPhoneNumberAsync(user, Input.PhoneNumber); + if (!setPhoneResult.Succeeded) + { + StatusMessage = "Unexpected error when trying to set phone number."; + return RedirectToPage(); + } + } + + await _signInManager.RefreshSignInAsync(user); + StatusMessage = "Your profile has been updated"; + return RedirectToPage(); + } + } +} diff --git a/Areas/Identity/Pages/Account/Manage/ManageNavPages.cs b/Areas/Identity/Pages/Account/Manage/ManageNavPages.cs new file mode 100644 index 0000000..ec4cd7a --- /dev/null +++ b/Areas/Identity/Pages/Account/Manage/ManageNavPages.cs @@ -0,0 +1,123 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +#nullable disable + +using System; +using Microsoft.AspNetCore.Mvc.Rendering; + +namespace PSTW_CentralSystem.Areas.Identity.Pages.Account.Manage +{ + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static class ManageNavPages + { + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static string Index => "Index"; + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static string Email => "Email"; + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static string ChangePassword => "ChangePassword"; + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static string DownloadPersonalData => "DownloadPersonalData"; + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static string DeletePersonalData => "DeletePersonalData"; + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static string ExternalLogins => "ExternalLogins"; + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static string PersonalData => "PersonalData"; + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static string TwoFactorAuthentication => "TwoFactorAuthentication"; + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static string IndexNavClass(ViewContext viewContext) => PageNavClass(viewContext, Index); + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static string EmailNavClass(ViewContext viewContext) => PageNavClass(viewContext, Email); + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static string ChangePasswordNavClass(ViewContext viewContext) => PageNavClass(viewContext, ChangePassword); + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static string DownloadPersonalDataNavClass(ViewContext viewContext) => PageNavClass(viewContext, DownloadPersonalData); + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static string DeletePersonalDataNavClass(ViewContext viewContext) => PageNavClass(viewContext, DeletePersonalData); + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static string ExternalLoginsNavClass(ViewContext viewContext) => PageNavClass(viewContext, ExternalLogins); + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static string PersonalDataNavClass(ViewContext viewContext) => PageNavClass(viewContext, PersonalData); + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static string TwoFactorAuthenticationNavClass(ViewContext viewContext) => PageNavClass(viewContext, TwoFactorAuthentication); + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static string PageNavClass(ViewContext viewContext, string page) + { + var activePage = viewContext.ViewData["ActivePage"] as string + ?? System.IO.Path.GetFileNameWithoutExtension(viewContext.ActionDescriptor.DisplayName); + return string.Equals(activePage, page, StringComparison.OrdinalIgnoreCase) ? "active" : null; + } + } +} diff --git a/Areas/Identity/Pages/Account/Manage/PersonalData.cshtml b/Areas/Identity/Pages/Account/Manage/PersonalData.cshtml new file mode 100644 index 0000000..0835493 --- /dev/null +++ b/Areas/Identity/Pages/Account/Manage/PersonalData.cshtml @@ -0,0 +1,27 @@ +@page +@model PersonalDataModel +@{ + ViewData["Title"] = "Personal Data"; + ViewData["ActivePage"] = ManageNavPages.PersonalData; +} + +

@ViewData["Title"]

+ +
+
+

Your account contains personal data that you have given us. This page allows you to download or delete that data.

+

+ Deleting this data will permanently remove your account, and this cannot be recovered. +

+
+ +
+

+ Delete +

+
+
+ +@section Scripts { + +} diff --git a/Areas/Identity/Pages/Account/Manage/PersonalData.cshtml.cs b/Areas/Identity/Pages/Account/Manage/PersonalData.cshtml.cs new file mode 100644 index 0000000..cb8ec56 --- /dev/null +++ b/Areas/Identity/Pages/Account/Manage/PersonalData.cshtml.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.Extensions.Logging; + +namespace PSTW_CentralSystem.Areas.Identity.Pages.Account.Manage +{ + public class PersonalDataModel : PageModel + { + private readonly UserManager _userManager; + private readonly ILogger _logger; + + public PersonalDataModel( + UserManager userManager, + ILogger logger) + { + _userManager = userManager; + _logger = logger; + } + + public async Task OnGet() + { + var user = await _userManager.GetUserAsync(User); + if (user == null) + { + return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); + } + + return Page(); + } + } +} diff --git a/Areas/Identity/Pages/Account/Manage/ResetAuthenticator.cshtml b/Areas/Identity/Pages/Account/Manage/ResetAuthenticator.cshtml new file mode 100644 index 0000000..f3297e0 --- /dev/null +++ b/Areas/Identity/Pages/Account/Manage/ResetAuthenticator.cshtml @@ -0,0 +1,24 @@ +@page +@model ResetAuthenticatorModel +@{ + ViewData["Title"] = "Reset authenticator key"; + ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication; +} + + +

@ViewData["Title"]

+ +
+
+ +
+
diff --git a/Areas/Identity/Pages/Account/Manage/ResetAuthenticator.cshtml.cs b/Areas/Identity/Pages/Account/Manage/ResetAuthenticator.cshtml.cs new file mode 100644 index 0000000..9300f92 --- /dev/null +++ b/Areas/Identity/Pages/Account/Manage/ResetAuthenticator.cshtml.cs @@ -0,0 +1,67 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +#nullable disable + +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.Extensions.Logging; + +namespace PSTW_CentralSystem.Areas.Identity.Pages.Account.Manage +{ + public class ResetAuthenticatorModel : PageModel + { + private readonly UserManager _userManager; + private readonly SignInManager _signInManager; + private readonly ILogger _logger; + + public ResetAuthenticatorModel( + UserManager userManager, + SignInManager signInManager, + ILogger logger) + { + _userManager = userManager; + _signInManager = signInManager; + _logger = logger; + } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [TempData] + public string StatusMessage { get; set; } + + public async Task OnGet() + { + var user = await _userManager.GetUserAsync(User); + if (user == null) + { + return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); + } + + return Page(); + } + + public async Task OnPostAsync() + { + var user = await _userManager.GetUserAsync(User); + if (user == null) + { + return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); + } + + await _userManager.SetTwoFactorEnabledAsync(user, false); + await _userManager.ResetAuthenticatorKeyAsync(user); + var userId = await _userManager.GetUserIdAsync(user); + _logger.LogInformation("User with ID '{UserId}' has reset their authentication app key.", user.Id); + + await _signInManager.RefreshSignInAsync(user); + StatusMessage = "Your authenticator app key has been reset, you will need to configure your authenticator app using the new key."; + + return RedirectToPage("./EnableAuthenticator"); + } + } +} diff --git a/Areas/Identity/Pages/Account/Manage/SetPassword.cshtml b/Areas/Identity/Pages/Account/Manage/SetPassword.cshtml new file mode 100644 index 0000000..a4ba058 --- /dev/null +++ b/Areas/Identity/Pages/Account/Manage/SetPassword.cshtml @@ -0,0 +1,35 @@ +@page +@model SetPasswordModel +@{ + ViewData["Title"] = "Set password"; + ViewData["ActivePage"] = ManageNavPages.ChangePassword; +} + +

Set your password

+ +

+ You do not have a local username/password for this site. Add a local + account so you can log in without an external login. +

+
+
+
+ +
+ + + +
+
+ + + +
+ +
+
+
+ +@section Scripts { + +} diff --git a/Areas/Identity/Pages/Account/Manage/SetPassword.cshtml.cs b/Areas/Identity/Pages/Account/Manage/SetPassword.cshtml.cs new file mode 100644 index 0000000..3c33a40 --- /dev/null +++ b/Areas/Identity/Pages/Account/Manage/SetPassword.cshtml.cs @@ -0,0 +1,114 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +#nullable disable + +using System; +using System.ComponentModel.DataAnnotations; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace PSTW_CentralSystem.Areas.Identity.Pages.Account.Manage +{ + public class SetPasswordModel : PageModel + { + private readonly UserManager _userManager; + private readonly SignInManager _signInManager; + + public SetPasswordModel( + UserManager userManager, + SignInManager signInManager) + { + _userManager = userManager; + _signInManager = signInManager; + } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [BindProperty] + public InputModel Input { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [TempData] + public string StatusMessage { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public class InputModel + { + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [Required] + [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] + [DataType(DataType.Password)] + [Display(Name = "New password")] + public string NewPassword { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [DataType(DataType.Password)] + [Display(Name = "Confirm new password")] + [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")] + public string ConfirmPassword { get; set; } + } + + public async Task OnGetAsync() + { + var user = await _userManager.GetUserAsync(User); + if (user == null) + { + return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); + } + + var hasPassword = await _userManager.HasPasswordAsync(user); + + if (hasPassword) + { + return RedirectToPage("./ChangePassword"); + } + + return Page(); + } + + public async Task OnPostAsync() + { + if (!ModelState.IsValid) + { + return Page(); + } + + var user = await _userManager.GetUserAsync(User); + if (user == null) + { + return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); + } + + var addPasswordResult = await _userManager.AddPasswordAsync(user, Input.NewPassword); + if (!addPasswordResult.Succeeded) + { + foreach (var error in addPasswordResult.Errors) + { + ModelState.AddModelError(string.Empty, error.Description); + } + return Page(); + } + + await _signInManager.RefreshSignInAsync(user); + StatusMessage = "Your password has been set."; + + return RedirectToPage(); + } + } +} diff --git a/Areas/Identity/Pages/Account/Manage/ShowRecoveryCodes.cshtml b/Areas/Identity/Pages/Account/Manage/ShowRecoveryCodes.cshtml new file mode 100644 index 0000000..49a8518 --- /dev/null +++ b/Areas/Identity/Pages/Account/Manage/ShowRecoveryCodes.cshtml @@ -0,0 +1,25 @@ +@page +@model ShowRecoveryCodesModel +@{ + ViewData["Title"] = "Recovery codes"; + ViewData["ActivePage"] = "TwoFactorAuthentication"; +} + + +

@ViewData["Title"]

+ +
+
+ @for (var row = 0; row < Model.RecoveryCodes.Length; row += 2) + { + @Model.RecoveryCodes[row] @Model.RecoveryCodes[row + 1]
+ } +
+
diff --git a/Areas/Identity/Pages/Account/Manage/ShowRecoveryCodes.cshtml.cs b/Areas/Identity/Pages/Account/Manage/ShowRecoveryCodes.cshtml.cs new file mode 100644 index 0000000..09e7bee --- /dev/null +++ b/Areas/Identity/Pages/Account/Manage/ShowRecoveryCodes.cshtml.cs @@ -0,0 +1,46 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +#nullable disable + +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.Extensions.Logging; + +namespace PSTW_CentralSystem.Areas.Identity.Pages.Account.Manage +{ + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public class ShowRecoveryCodesModel : PageModel + { + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [TempData] + public string[] RecoveryCodes { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [TempData] + public string StatusMessage { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public IActionResult OnGet() + { + if (RecoveryCodes == null || RecoveryCodes.Length == 0) + { + return RedirectToPage("./TwoFactorAuthentication"); + } + + return Page(); + } + } +} diff --git a/Areas/Identity/Pages/Account/Manage/TwoFactorAuthentication.cshtml b/Areas/Identity/Pages/Account/Manage/TwoFactorAuthentication.cshtml new file mode 100644 index 0000000..6db5b32 --- /dev/null +++ b/Areas/Identity/Pages/Account/Manage/TwoFactorAuthentication.cshtml @@ -0,0 +1,71 @@ +@page +@using Microsoft.AspNetCore.Http.Features +@model TwoFactorAuthenticationModel +@{ + ViewData["Title"] = "Two-factor authentication (2FA)"; + ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication; +} + + +

@ViewData["Title"]

+@{ + var consentFeature = HttpContext.Features.Get(); + @if (consentFeature?.CanTrack ?? true) + { + @if (Model.Is2faEnabled) + { + if (Model.RecoveryCodesLeft == 0) + { +
+ You have no recovery codes left. +

You must generate a new set of recovery codes before you can log in with a recovery code.

+
+ } + else if (Model.RecoveryCodesLeft == 1) + { +
+ You have 1 recovery code left. +

You can generate a new set of recovery codes.

+
+ } + else if (Model.RecoveryCodesLeft <= 3) + { +
+ You have @Model.RecoveryCodesLeft recovery codes left. +

You should generate a new set of recovery codes.

+
+ } + + if (Model.IsMachineRemembered) + { +
+ +
+ } + Disable 2FA + Reset recovery codes + } + +

Authenticator app

+ @if (!Model.HasAuthenticator) + { + Add authenticator app + } + else + { + Set up authenticator app + Reset authenticator app + } + } + else + { +
+ Privacy and cookie policy have not been accepted. +

You must accept the policy before you can enable two factor authentication.

+
+ } +} + +@section Scripts { + +} diff --git a/Areas/Identity/Pages/Account/Manage/TwoFactorAuthentication.cshtml.cs b/Areas/Identity/Pages/Account/Manage/TwoFactorAuthentication.cshtml.cs new file mode 100644 index 0000000..a1e08c0 --- /dev/null +++ b/Areas/Identity/Pages/Account/Manage/TwoFactorAuthentication.cshtml.cs @@ -0,0 +1,89 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +#nullable disable + +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.Extensions.Logging; + +namespace PSTW_CentralSystem.Areas.Identity.Pages.Account.Manage +{ + public class TwoFactorAuthenticationModel : PageModel + { + private readonly UserManager _userManager; + private readonly SignInManager _signInManager; + private readonly ILogger _logger; + + public TwoFactorAuthenticationModel( + UserManager userManager, SignInManager signInManager, ILogger logger) + { + _userManager = userManager; + _signInManager = signInManager; + _logger = logger; + } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public bool HasAuthenticator { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public int RecoveryCodesLeft { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [BindProperty] + public bool Is2faEnabled { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public bool IsMachineRemembered { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [TempData] + public string StatusMessage { get; set; } + + public async Task OnGetAsync() + { + var user = await _userManager.GetUserAsync(User); + if (user == null) + { + return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); + } + + HasAuthenticator = await _userManager.GetAuthenticatorKeyAsync(user) != null; + Is2faEnabled = await _userManager.GetTwoFactorEnabledAsync(user); + IsMachineRemembered = await _signInManager.IsTwoFactorClientRememberedAsync(user); + RecoveryCodesLeft = await _userManager.CountRecoveryCodesAsync(user); + + return Page(); + } + + public async Task OnPostAsync() + { + var user = await _userManager.GetUserAsync(User); + if (user == null) + { + return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); + } + + await _signInManager.ForgetTwoFactorClientAsync(); + StatusMessage = "The current browser has been forgotten. When you login again from this browser you will be prompted for your 2fa code."; + return RedirectToPage(); + } + } +} diff --git a/Areas/Identity/Pages/Account/Manage/_Layout.cshtml b/Areas/Identity/Pages/Account/Manage/_Layout.cshtml new file mode 100644 index 0000000..fb77735 --- /dev/null +++ b/Areas/Identity/Pages/Account/Manage/_Layout.cshtml @@ -0,0 +1,29 @@ +@{ + if (ViewData.TryGetValue("ParentLayout", out var parentLayout) && parentLayout != null) + { + Layout = parentLayout.ToString(); + } + else + { + Layout = "/Areas/Identity/Pages/_Layout.cshtml"; + } +} + +

Manage your account

+ +
+

Change your account settings

+
+
+
+ +
+
+ @RenderBody() +
+
+
+ +@section Scripts { + @RenderSection("Scripts", required: false) +} diff --git a/Areas/Identity/Pages/Account/Manage/_ManageNav.cshtml b/Areas/Identity/Pages/Account/Manage/_ManageNav.cshtml new file mode 100644 index 0000000..59b1bb1 --- /dev/null +++ b/Areas/Identity/Pages/Account/Manage/_ManageNav.cshtml @@ -0,0 +1,15 @@ +@inject SignInManager SignInManager +@{ + var hasExternalLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync()).Any(); +} + diff --git a/Areas/Identity/Pages/Account/Manage/_StatusMessage.cshtml b/Areas/Identity/Pages/Account/Manage/_StatusMessage.cshtml new file mode 100644 index 0000000..c898543 --- /dev/null +++ b/Areas/Identity/Pages/Account/Manage/_StatusMessage.cshtml @@ -0,0 +1,10 @@ +@model string + +@if (!String.IsNullOrEmpty(Model)) +{ + var statusMessageClass = Model.StartsWith("Error") ? "danger" : "success"; + +} diff --git a/Areas/Identity/Pages/Account/Manage/_ViewImports.cshtml b/Areas/Identity/Pages/Account/Manage/_ViewImports.cshtml new file mode 100644 index 0000000..92d34b1 --- /dev/null +++ b/Areas/Identity/Pages/Account/Manage/_ViewImports.cshtml @@ -0,0 +1 @@ +@using PSTW_CentralSystem.Areas.Identity.Pages.Account.Manage \ No newline at end of file diff --git a/Areas/Identity/Pages/Account/Register.cshtml b/Areas/Identity/Pages/Account/Register.cshtml new file mode 100644 index 0000000..6fbfc8f --- /dev/null +++ b/Areas/Identity/Pages/Account/Register.cshtml @@ -0,0 +1,67 @@ +@page +@model RegisterModel +@{ + ViewData["Title"] = "Register"; +} + +

@ViewData["Title"]

+ +
+
+
+

Create a new account.

+
+ +
+ + + +
+
+ + + +
+
+ + + +
+ +
+
+
+
+

Use another service to register.

+
+ @{ + if ((Model.ExternalLogins?.Count ?? 0) == 0) + { +
+

+ There are no external authentication services configured. See this article + about setting up this ASP.NET application to support logging in via external services. +

+
+ } + else + { +
+
+

+ @foreach (var provider in Model.ExternalLogins!) + { + + } +

+
+
+ } + } +
+
+
+ +@section Scripts { + +} diff --git a/Areas/Identity/Pages/Account/Register.cshtml.cs b/Areas/Identity/Pages/Account/Register.cshtml.cs new file mode 100644 index 0000000..1945ef9 --- /dev/null +++ b/Areas/Identity/Pages/Account/Register.cshtml.cs @@ -0,0 +1,180 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +#nullable disable + +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text; +using System.Text.Encodings.Web; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity.UI.Services; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.AspNetCore.WebUtilities; +using Microsoft.Extensions.Logging; + +namespace PSTW_CentralSystem.Areas.Identity.Pages.Account +{ + public class RegisterModel : PageModel + { + private readonly SignInManager _signInManager; + private readonly UserManager _userManager; + private readonly IUserStore _userStore; + private readonly IUserEmailStore _emailStore; + private readonly ILogger _logger; + private readonly IEmailSender _emailSender; + + public RegisterModel( + UserManager userManager, + IUserStore userStore, + SignInManager signInManager, + ILogger logger, + IEmailSender emailSender) + { + _userManager = userManager; + _userStore = userStore; + _emailStore = GetEmailStore(); + _signInManager = signInManager; + _logger = logger; + _emailSender = emailSender; + } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [BindProperty] + public InputModel Input { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public string ReturnUrl { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public IList ExternalLogins { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public class InputModel + { + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [Required] + [EmailAddress] + [Display(Name = "Email")] + public string Email { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [Required] + [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] + [DataType(DataType.Password)] + [Display(Name = "Password")] + public string Password { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [DataType(DataType.Password)] + [Display(Name = "Confirm password")] + [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] + public string ConfirmPassword { get; set; } + } + + + public async Task OnGetAsync(string returnUrl = null) + { + ReturnUrl = returnUrl; + ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList(); + } + + public async Task OnPostAsync(string returnUrl = null) + { + returnUrl ??= Url.Content("~/"); + ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList(); + if (ModelState.IsValid) + { + var user = CreateUser(); + + await _userStore.SetUserNameAsync(user, Input.Email, CancellationToken.None); + await _emailStore.SetEmailAsync(user, Input.Email, CancellationToken.None); + var result = await _userManager.CreateAsync(user, Input.Password); + + if (result.Succeeded) + { + _logger.LogInformation("User created a new account with password."); + + var userId = await _userManager.GetUserIdAsync(user); + var code = await _userManager.GenerateEmailConfirmationTokenAsync(user); + code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code)); + var callbackUrl = Url.Page( + "/Account/ConfirmEmail", + pageHandler: null, + values: new { area = "Identity", userId = userId, code = code, returnUrl = returnUrl }, + protocol: Request.Scheme); + + await _emailSender.SendEmailAsync(Input.Email, "Confirm your email", + $"Please confirm your account by clicking here."); + + if (_userManager.Options.SignIn.RequireConfirmedAccount) + { + return RedirectToPage("RegisterConfirmation", new { email = Input.Email, returnUrl = returnUrl }); + } + else + { + await _signInManager.SignInAsync(user, isPersistent: false); + return LocalRedirect(returnUrl); + } + } + foreach (var error in result.Errors) + { + ModelState.AddModelError(string.Empty, error.Description); + } + } + + // If we got this far, something failed, redisplay form + return Page(); + } + + private IdentityUser CreateUser() + { + try + { + return Activator.CreateInstance(); + } + catch + { + throw new InvalidOperationException($"Can't create an instance of '{nameof(IdentityUser)}'. " + + $"Ensure that '{nameof(IdentityUser)}' is not an abstract class and has a parameterless constructor, or alternatively " + + $"override the register page in /Areas/Identity/Pages/Account/Register.cshtml"); + } + } + + private IUserEmailStore GetEmailStore() + { + if (!_userManager.SupportsUserEmail) + { + throw new NotSupportedException("The default UI requires a user store with email support."); + } + return (IUserEmailStore)_userStore; + } + } +} diff --git a/Areas/Identity/Pages/Account/RegisterConfirmation.cshtml b/Areas/Identity/Pages/Account/RegisterConfirmation.cshtml new file mode 100644 index 0000000..d3e7f61 --- /dev/null +++ b/Areas/Identity/Pages/Account/RegisterConfirmation.cshtml @@ -0,0 +1,23 @@ +@page +@model RegisterConfirmationModel +@{ + ViewData["Title"] = "Register confirmation"; +} + +

@ViewData["Title"]

+@{ + if (@Model.DisplayConfirmAccountLink) + { +

+ This app does not currently have a real email sender registered, see these docs for how to configure a real email sender. + Normally this would be emailed: Click here to confirm your account +

+ } + else + { +

+ Please check your email to confirm your account. +

+ } +} + diff --git a/Areas/Identity/Pages/Account/RegisterConfirmation.cshtml.cs b/Areas/Identity/Pages/Account/RegisterConfirmation.cshtml.cs new file mode 100644 index 0000000..dfceefa --- /dev/null +++ b/Areas/Identity/Pages/Account/RegisterConfirmation.cshtml.cs @@ -0,0 +1,79 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +#nullable disable + +using System; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity.UI.Services; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.AspNetCore.WebUtilities; + +namespace PSTW_CentralSystem.Areas.Identity.Pages.Account +{ + [AllowAnonymous] + public class RegisterConfirmationModel : PageModel + { + private readonly UserManager _userManager; + private readonly IEmailSender _sender; + + public RegisterConfirmationModel(UserManager userManager, IEmailSender sender) + { + _userManager = userManager; + _sender = sender; + } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public string Email { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public bool DisplayConfirmAccountLink { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public string EmailConfirmationUrl { get; set; } + + public async Task OnGetAsync(string email, string returnUrl = null) + { + if (email == null) + { + return RedirectToPage("/Index"); + } + returnUrl = returnUrl ?? Url.Content("~/"); + + var user = await _userManager.FindByEmailAsync(email); + if (user == null) + { + return NotFound($"Unable to load user with email '{email}'."); + } + + Email = email; + // Once you add a real email sender, you should remove this code that lets you confirm the account + DisplayConfirmAccountLink = true; + if (DisplayConfirmAccountLink) + { + var userId = await _userManager.GetUserIdAsync(user); + var code = await _userManager.GenerateEmailConfirmationTokenAsync(user); + code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code)); + EmailConfirmationUrl = Url.Page( + "/Account/ConfirmEmail", + pageHandler: null, + values: new { area = "Identity", userId = userId, code = code, returnUrl = returnUrl }, + protocol: Request.Scheme); + } + + return Page(); + } + } +} diff --git a/Areas/Identity/Pages/Account/ResendEmailConfirmation.cshtml b/Areas/Identity/Pages/Account/ResendEmailConfirmation.cshtml new file mode 100644 index 0000000..d457b6b --- /dev/null +++ b/Areas/Identity/Pages/Account/ResendEmailConfirmation.cshtml @@ -0,0 +1,26 @@ +@page +@model ResendEmailConfirmationModel +@{ + ViewData["Title"] = "Resend email confirmation"; +} + +

@ViewData["Title"]

+

Enter your email.

+
+
+
+
+ +
+ + + +
+ +
+
+
+ +@section Scripts { + +} diff --git a/Areas/Identity/Pages/Account/ResendEmailConfirmation.cshtml.cs b/Areas/Identity/Pages/Account/ResendEmailConfirmation.cshtml.cs new file mode 100644 index 0000000..65bb3a9 --- /dev/null +++ b/Areas/Identity/Pages/Account/ResendEmailConfirmation.cshtml.cs @@ -0,0 +1,88 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +#nullable disable + +using System; +using System.ComponentModel.DataAnnotations; +using System.Text; +using System.Text.Encodings.Web; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Identity.UI.Services; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.AspNetCore.WebUtilities; + +namespace PSTW_CentralSystem.Areas.Identity.Pages.Account +{ + [AllowAnonymous] + public class ResendEmailConfirmationModel : PageModel + { + private readonly UserManager _userManager; + private readonly IEmailSender _emailSender; + + public ResendEmailConfirmationModel(UserManager userManager, IEmailSender emailSender) + { + _userManager = userManager; + _emailSender = emailSender; + } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [BindProperty] + public InputModel Input { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public class InputModel + { + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [Required] + [EmailAddress] + public string Email { get; set; } + } + + public void OnGet() + { + } + + public async Task OnPostAsync() + { + if (!ModelState.IsValid) + { + return Page(); + } + + var user = await _userManager.FindByEmailAsync(Input.Email); + if (user == null) + { + ModelState.AddModelError(string.Empty, "Verification email sent. Please check your email."); + return Page(); + } + + var userId = await _userManager.GetUserIdAsync(user); + var code = await _userManager.GenerateEmailConfirmationTokenAsync(user); + code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code)); + var callbackUrl = Url.Page( + "/Account/ConfirmEmail", + pageHandler: null, + values: new { userId = userId, code = code }, + protocol: Request.Scheme); + await _emailSender.SendEmailAsync( + Input.Email, + "Confirm your email", + $"Please confirm your account by clicking here."); + + ModelState.AddModelError(string.Empty, "Verification email sent. Please check your email."); + return Page(); + } + } +} diff --git a/Areas/Identity/Pages/Account/ResetPassword.cshtml b/Areas/Identity/Pages/Account/ResetPassword.cshtml new file mode 100644 index 0000000..e430d01 --- /dev/null +++ b/Areas/Identity/Pages/Account/ResetPassword.cshtml @@ -0,0 +1,37 @@ +@page +@model ResetPasswordModel +@{ + ViewData["Title"] = "Reset password"; +} + +

@ViewData["Title"]

+

Reset your password.

+
+
+
+
+ + +
+ + + +
+
+ + + +
+
+ + + +
+ +
+
+
+ +@section Scripts { + +} diff --git a/Areas/Identity/Pages/Account/ResetPassword.cshtml.cs b/Areas/Identity/Pages/Account/ResetPassword.cshtml.cs new file mode 100644 index 0000000..6b750a7 --- /dev/null +++ b/Areas/Identity/Pages/Account/ResetPassword.cshtml.cs @@ -0,0 +1,117 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +#nullable disable + +using System; +using System.ComponentModel.DataAnnotations; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.AspNetCore.WebUtilities; + +namespace PSTW_CentralSystem.Areas.Identity.Pages.Account +{ + public class ResetPasswordModel : PageModel + { + private readonly UserManager _userManager; + + public ResetPasswordModel(UserManager userManager) + { + _userManager = userManager; + } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [BindProperty] + public InputModel Input { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public class InputModel + { + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [Required] + [EmailAddress] + public string Email { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [Required] + [StringLength(100, ErrorMessage = "The {0} must be at least {2} and at max {1} characters long.", MinimumLength = 6)] + [DataType(DataType.Password)] + public string Password { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [DataType(DataType.Password)] + [Display(Name = "Confirm password")] + [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] + public string ConfirmPassword { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [Required] + public string Code { get; set; } + + } + + public IActionResult OnGet(string code = null) + { + if (code == null) + { + return BadRequest("A code must be supplied for password reset."); + } + else + { + Input = new InputModel + { + Code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(code)) + }; + return Page(); + } + } + + public async Task OnPostAsync() + { + if (!ModelState.IsValid) + { + return Page(); + } + + var user = await _userManager.FindByEmailAsync(Input.Email); + if (user == null) + { + // Don't reveal that the user does not exist + return RedirectToPage("./ResetPasswordConfirmation"); + } + + var result = await _userManager.ResetPasswordAsync(user, Input.Code, Input.Password); + if (result.Succeeded) + { + return RedirectToPage("./ResetPasswordConfirmation"); + } + + foreach (var error in result.Errors) + { + ModelState.AddModelError(string.Empty, error.Description); + } + return Page(); + } + } +} diff --git a/Areas/Identity/Pages/Account/ResetPasswordConfirmation.cshtml b/Areas/Identity/Pages/Account/ResetPasswordConfirmation.cshtml new file mode 100644 index 0000000..c52552f --- /dev/null +++ b/Areas/Identity/Pages/Account/ResetPasswordConfirmation.cshtml @@ -0,0 +1,10 @@ +@page +@model ResetPasswordConfirmationModel +@{ + ViewData["Title"] = "Reset password confirmation"; +} + +

@ViewData["Title"]

+

+ Your password has been reset. Please click here to log in. +

diff --git a/Areas/Identity/Pages/Account/ResetPasswordConfirmation.cshtml.cs b/Areas/Identity/Pages/Account/ResetPasswordConfirmation.cshtml.cs new file mode 100644 index 0000000..29100f2 --- /dev/null +++ b/Areas/Identity/Pages/Account/ResetPasswordConfirmation.cshtml.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +#nullable disable + +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace PSTW_CentralSystem.Areas.Identity.Pages.Account +{ + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [AllowAnonymous] + public class ResetPasswordConfirmationModel : PageModel + { + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public void OnGet() + { + } + } +} diff --git a/Areas/Identity/Pages/Account/_StatusMessage.cshtml b/Areas/Identity/Pages/Account/_StatusMessage.cshtml new file mode 100644 index 0000000..c898543 --- /dev/null +++ b/Areas/Identity/Pages/Account/_StatusMessage.cshtml @@ -0,0 +1,10 @@ +@model string + +@if (!String.IsNullOrEmpty(Model)) +{ + var statusMessageClass = Model.StartsWith("Error") ? "danger" : "success"; + +} diff --git a/Areas/Identity/Pages/Account/_ViewImports.cshtml b/Areas/Identity/Pages/Account/_ViewImports.cshtml new file mode 100644 index 0000000..2876e11 --- /dev/null +++ b/Areas/Identity/Pages/Account/_ViewImports.cshtml @@ -0,0 +1 @@ +@using PSTW_CentralSystem.Areas.Identity.Pages.Account \ No newline at end of file diff --git a/Areas/Identity/Pages/Error.cshtml b/Areas/Identity/Pages/Error.cshtml new file mode 100644 index 0000000..b1f3143 --- /dev/null +++ b/Areas/Identity/Pages/Error.cshtml @@ -0,0 +1,23 @@ +@page +@model ErrorModel +@{ + ViewData["Title"] = "Error"; +} + +

Error.

+

An error occurred while processing your request.

+ +@if (Model.ShowRequestId) +{ +

+ Request ID: @Model.RequestId +

+} + +

Development Mode

+

+ Swapping to Development environment will display more detailed information about the error that occurred. +

+

+ Development environment should not be enabled in deployed applications, as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the ASPNETCORE_ENVIRONMENT environment variable to Development, and restarting the application. +

diff --git a/Areas/Identity/Pages/Error.cshtml.cs b/Areas/Identity/Pages/Error.cshtml.cs new file mode 100644 index 0000000..a6ab43a --- /dev/null +++ b/Areas/Identity/Pages/Error.cshtml.cs @@ -0,0 +1,41 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +#nullable disable + +using System.Diagnostics; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace PSTW_CentralSystem.Areas.Identity.Pages +{ + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + [AllowAnonymous] + [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] + public class ErrorModel : PageModel + { + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public string RequestId { get; set; } + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); + + /// + /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public void OnGet() + { + RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; + } + } +} diff --git a/Areas/Identity/Pages/_ValidationScriptsPartial.cshtml b/Areas/Identity/Pages/_ValidationScriptsPartial.cshtml new file mode 100644 index 0000000..5a16d80 --- /dev/null +++ b/Areas/Identity/Pages/_ValidationScriptsPartial.cshtml @@ -0,0 +1,2 @@ + + diff --git a/Areas/Identity/Pages/_ViewImports.cshtml b/Areas/Identity/Pages/_ViewImports.cshtml new file mode 100644 index 0000000..a9632f1 --- /dev/null +++ b/Areas/Identity/Pages/_ViewImports.cshtml @@ -0,0 +1,4 @@ +@using Microsoft.AspNetCore.Identity +@using PSTW_CentralSystem.Areas.Identity +@using PSTW_CentralSystem.Areas.Identity.Pages +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers diff --git a/Areas/Identity/Pages/_ViewStart.cshtml b/Areas/Identity/Pages/_ViewStart.cshtml new file mode 100644 index 0000000..94fd419 --- /dev/null +++ b/Areas/Identity/Pages/_ViewStart.cshtml @@ -0,0 +1,4 @@ + +@{ + Layout = "/Views/Shared/_Layout.cshtml"; +} diff --git a/Controllers/HomeController.cs b/Controllers/HomeController.cs index a3c8dd2..884410d 100644 --- a/Controllers/HomeController.cs +++ b/Controllers/HomeController.cs @@ -1,9 +1,11 @@ +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using PSTW_CentralSystem.Models; using System.Diagnostics; namespace PSTW_CentralSystem.Controllers { + [Authorize(Policy = "RoleModulePolicy")] public class HomeController : Controller { private readonly ILogger _logger; @@ -17,6 +19,10 @@ namespace PSTW_CentralSystem.Controllers { return View(); } + public IActionResult Index2() + { + return View(); + } public IActionResult Privacy() { diff --git a/CustomPolicy/RoleModulePolicy.cs b/CustomPolicy/RoleModulePolicy.cs new file mode 100644 index 0000000..1a1adbf --- /dev/null +++ b/CustomPolicy/RoleModulePolicy.cs @@ -0,0 +1,34 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity; +using PSTW_CentralSystem.DBContext; + +namespace PSTW_CentralSystem.CustomPolicy +{ + public class RoleModulePolicy : IAuthorizationRequirement + { + + } + public class RoleModuleHandler : AuthorizationHandler + { + private readonly AuthDBContext _authDBContext; + private readonly UserManager _userManager; + private readonly RoleManager _roleManager; + private readonly IHttpContextAccessor _httpContextAccessor; + public RoleModuleHandler( AuthDBContext authDBContext, UserManager userManager, RoleManager roleManager, IHttpContextAccessor httpContextAccessor) + { + _authDBContext = authDBContext; + _userManager = userManager; + _roleManager = roleManager; + _httpContextAccessor = httpContextAccessor; + } + protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, RoleModulePolicy requirement) + { + // Get the current user + var currentUser = await _userManager.GetUserAsync(context.User); + var userRole = await _userManager.GetRolesAsync(currentUser ?? new IdentityUser()); + var moduleName = _httpContextAccessor.HttpContext?.GetRouteData().Values["controller"]?.ToString(); + var regModule = _httpContextAccessor.HttpContext?.GetRouteData().Values["page"]?.ToString(); + context.Fail(); + } + } +} diff --git a/Models/ModuleSettingModel.cs b/Models/ModuleSettingModel.cs new file mode 100644 index 0000000..6642239 --- /dev/null +++ b/Models/ModuleSettingModel.cs @@ -0,0 +1,26 @@ +using Microsoft.EntityFrameworkCore.Metadata.Internal; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +namespace PSTW_CentralSystem.Models +{ + //[Table("modulesettings")] + public class ModuleSettingModel + { + [Key] + [Required] + public int SettingId { get; set; } + + [Required] + [MaxLength(50)] + public string ModuleName { get; set; } + [Column(TypeName = "json")] + public string? AllowedUsertype { get; set; } + public int ModuleStatus { get; set; } + public string? Description { get; set; } + + [NotMapped] // This property will not be mapped to the database + public string[]? AllowedUsertypesArray { get; set; } + //[NotMapped] + //public List? RoleList { get; set; } + } +} diff --git a/PSTW_CentralSystem.csproj b/PSTW_CentralSystem.csproj index fea17be..8f86a7d 100644 --- a/PSTW_CentralSystem.csproj +++ b/PSTW_CentralSystem.csproj @@ -14,13 +14,20 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + + all runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + diff --git a/Program.cs b/Program.cs index ea5b02b..bddd51c 100644 --- a/Program.cs +++ b/Program.cs @@ -1,5 +1,7 @@ +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; +using PSTW_CentralSystem.CustomPolicy; using PSTW_CentralSystem.DBContext; using Serilog; @@ -31,6 +33,20 @@ builder.Services.AddDbContext(options => options.UseMySQL(config.GetConnectionString("DefaultConnection") ?? string.Empty, mysqlOptions => mysqlOptions.CommandTimeout(300)); }); +//builder.Services.AddDefaultIdentity(options => options.SignIn.RequireConfirmedAccount = true).AddEntityFrameworkStores(); +builder.Services.AddIdentity(options => options.SignIn.RequireConfirmedAccount = true) + .AddEntityFrameworkStores() + .AddDefaultUI() + .AddDefaultTokenProviders(); + +builder.Services.AddAuthorizationCore(options => +{ + options.AddPolicy("RoleModulePolicy", policy => + policy.Requirements.Add(new RoleModulePolicy())); +}); + +// Add scope +builder.Services.AddScoped(); var app = builder.Build(); @@ -48,7 +64,7 @@ app.UseStaticFiles(); app.UseRouting(); app.UseAuthorization(); - +app.MapRazorPages(); app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); diff --git a/Views/Home/Index2.cshtml b/Views/Home/Index2.cshtml new file mode 100644 index 0000000..dbef6d1 --- /dev/null +++ b/Views/Home/Index2.cshtml @@ -0,0 +1,1259 @@ +@* + For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860 +*@ +@{ +} + + + + + + + + + + + + + Matrix Admin Lite Free Versions Template by WrapPixel + + + + + + + + + + + + + + + +
+
+
+
+
+
+ + + +
+ + + +
+ +
+ + + + + + + + + + + + + +
+ + + + + + + + + + +
+ + + +
+ +
+
+
+

+ +

+
Dashboard
+
+
+
+ +
+
+
+

+ +

+
Charts
+
+
+
+ +
+
+
+

+ +

+
Widgets
+
+
+
+ +
+
+
+

+ +

+
Tables
+
+
+
+
+
+
+
+
+

Latest Posts

+
+
+ +
+
+ user +
+
+
James Anderson
+ + Lorem Ipsum is simply dummy text of the printing and + type setting industry. + + +
+
+ +
+
+ user +
+
+
Michael Jorden
+ + Lorem Ipsum is simply dummy text of the printing and + type setting industry. + + +
+
+ +
+
+ user +
+
+
Johnathan Doeting
+ + Lorem Ipsum is simply dummy text of the printing and + type setting industry. + + +
+
+
+
+ +
+
+

To Do List

+
+
    +
  • +
    + + +
    +
      +
    • + user +
    • +
    • + user +
    • +
    • + user +
    • +
    • + user +
    • +
    +
  • +
  • +
    + + +
    +
    26 jun 2021
    +
  • +
  • +
    + + +
    +
      +
    • + user +
    • +
    • + user +
    • +
    +
  • +
  • +
    + + +
    +
    26 jun 2021
    +
  • +
  • +
    + + +
    +
      +
    • + user +
    • +
    • + user +
    • +
    +
  • +
+
+
+
+
+
+ +
+
+

Progress Box

+
+
+ 81% Clicks +
+ 125 +
+
+
+
+
+
+
+
+ 72% Uniquie Clicks +
+ 120 +
+
+
+
+
+
+
+
+ 53% Impressions +
+ 785 +
+
+
+
+
+
+
+
+ 3% Online Users +
+ 8 +
+
+
+
+
+
+
+
+ +
+
+

News Updates

+
+ +
+
+
+
+
+
Calender
+
+
+
+
+
+

Drag & Drop Event

+
+
+
+
+
+
+ Event + One +
+
+ + Event Two +
+
+ Event Three +
+
+ Event Four +
+
+ +
+ + +
+ + Add New + Event + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + +
+ + + + + + +
+ All Rights Reserved by Matrix-admin. Designed and Developed by + WrapPixel. +
+ + + +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Views/Shared/_Layout - Backup.cshtml b/Views/Shared/_Layout - Backup.cshtml new file mode 100644 index 0000000..f34b31f --- /dev/null +++ b/Views/Shared/_Layout - Backup.cshtml @@ -0,0 +1,50 @@ + + + + + + @ViewData["Title"] - PSTW_CentralSystem + + + + + +
+ +
+
+
+ @RenderBody() +
+
+ +
+
+ © 2024 - PSTW_CentralSystem - Privacy +
+
+ + + + @await RenderSectionAsync("Scripts", required: false) + + diff --git a/Views/Shared/_Layout - Backup.cshtml.css b/Views/Shared/_Layout - Backup.cshtml.css new file mode 100644 index 0000000..c187c02 --- /dev/null +++ b/Views/Shared/_Layout - Backup.cshtml.css @@ -0,0 +1,48 @@ +/* Please see documentation at https://learn.microsoft.com/aspnet/core/client-side/bundling-and-minification +for details on configuring this project to bundle and minify static web assets. */ + +a.navbar-brand { + white-space: normal; + text-align: center; + word-break: break-all; +} + +a { + color: #0077cc; +} + +.btn-primary { + color: #fff; + background-color: #1b6ec2; + border-color: #1861ac; +} + +.nav-pills .nav-link.active, .nav-pills .show > .nav-link { + color: #fff; + background-color: #1b6ec2; + border-color: #1861ac; +} + +.border-top { + border-top: 1px solid #e5e5e5; +} +.border-bottom { + border-bottom: 1px solid #e5e5e5; +} + +.box-shadow { + box-shadow: 0 .25rem .75rem rgba(0, 0, 0, .05); +} + +button.accept-policy { + font-size: 1rem; + line-height: inherit; +} + +.footer { + position: absolute; + bottom: 0; + width: 100%; + white-space: nowrap; + line-height: 60px; +} diff --git a/Views/Shared/_Layout.cshtml b/Views/Shared/_Layout.cshtml index f4cf6a2..b93b427 100644 --- a/Views/Shared/_Layout.cshtml +++ b/Views/Shared/_Layout.cshtml @@ -1,49 +1,609 @@ - - +@* + For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860 +*@ +@{ +} + + + + - - @ViewData["Title"] - PSTW_CentralSystem - - - + + + + + + + Matrix Admin Lite Free Versions Template by WrapPixel + + + + + + + + + + -
- -
-
-
- @RenderBody() -
-
- -
-
- © 2024 - PSTW_CentralSystem - Privacy + + + + + + +
+ + + +
+ @RenderBody() +
+ + + + +
+ + + + + + +
+ All Rights Reserved by Matrix-admin. Designed and Developed by + WrapPixel. +
+ + +
-
- - - - @await RenderSectionAsync("Scripts", required: false) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @await RenderSectionAsync("Scripts", required: false) diff --git a/Views/Shared/_LoginPartial.cshtml b/Views/Shared/_LoginPartial.cshtml new file mode 100644 index 0000000..000a173 --- /dev/null +++ b/Views/Shared/_LoginPartial.cshtml @@ -0,0 +1,27 @@ +@using Microsoft.AspNetCore.Identity + +@inject SignInManager SignInManager +@inject UserManager UserManager + +@* *@ diff --git a/wwwroot/assets/extra-libs/DataTables/DataTables-1.10.16/css/dataTables.bootstrap.css b/wwwroot/assets/extra-libs/DataTables/DataTables-1.10.16/css/dataTables.bootstrap.css new file mode 100644 index 0000000..6a9e753 --- /dev/null +++ b/wwwroot/assets/extra-libs/DataTables/DataTables-1.10.16/css/dataTables.bootstrap.css @@ -0,0 +1,187 @@ +table.dataTable { + clear: both; + margin-top: 6px !important; + margin-bottom: 6px !important; + max-width: none !important; + border-collapse: separate !important; +} +table.dataTable td, +table.dataTable th { + -webkit-box-sizing: content-box; + box-sizing: content-box; +} +table.dataTable td.dataTables_empty, +table.dataTable th.dataTables_empty { + text-align: center; +} +table.dataTable.nowrap th, +table.dataTable.nowrap td { + white-space: nowrap; +} + +div.dataTables_wrapper div.dataTables_length label { + font-weight: normal; + text-align: left; + white-space: nowrap; +} +div.dataTables_wrapper div.dataTables_length select { + width: 75px; + display: inline-block; +} +div.dataTables_wrapper div.dataTables_filter { + text-align: right; +} +div.dataTables_wrapper div.dataTables_filter label { + font-weight: normal; + white-space: nowrap; + text-align: left; +} +div.dataTables_wrapper div.dataTables_filter input { + margin-left: 0.5em; + display: inline-block; + width: auto; +} +div.dataTables_wrapper div.dataTables_info { + padding-top: 8px; + white-space: nowrap; +} +div.dataTables_wrapper div.dataTables_paginate { + margin: 0; + white-space: nowrap; + text-align: right; +} +div.dataTables_wrapper div.dataTables_paginate ul.pagination { + margin: 2px 0; + white-space: nowrap; +} +div.dataTables_wrapper div.dataTables_processing { + position: absolute; + top: 50%; + left: 50%; + width: 200px; + margin-left: -100px; + margin-top: -26px; + text-align: center; + padding: 1em 0; +} + +table.dataTable thead > tr > th.sorting_asc, table.dataTable thead > tr > th.sorting_desc, table.dataTable thead > tr > th.sorting, +table.dataTable thead > tr > td.sorting_asc, +table.dataTable thead > tr > td.sorting_desc, +table.dataTable thead > tr > td.sorting { + padding-right: 30px; +} +table.dataTable thead > tr > th:active, +table.dataTable thead > tr > td:active { + outline: none; +} +table.dataTable thead .sorting, +table.dataTable thead .sorting_asc, +table.dataTable thead .sorting_desc, +table.dataTable thead .sorting_asc_disabled, +table.dataTable thead .sorting_desc_disabled { + cursor: pointer; + position: relative; +} +table.dataTable thead .sorting:after, +table.dataTable thead .sorting_asc:after, +table.dataTable thead .sorting_desc:after, +table.dataTable thead .sorting_asc_disabled:after, +table.dataTable thead .sorting_desc_disabled:after { + position: absolute; + bottom: 8px; + right: 8px; + display: block; + font-family: 'Glyphicons Halflings'; + opacity: 0.5; +} +table.dataTable thead .sorting:after { + opacity: 0.2; + content: "\e150"; + /* sort */ +} +table.dataTable thead .sorting_asc:after { + content: "\e155"; + /* sort-by-attributes */ +} +table.dataTable thead .sorting_desc:after { + content: "\e156"; + /* sort-by-attributes-alt */ +} +table.dataTable thead .sorting_asc_disabled:after, +table.dataTable thead .sorting_desc_disabled:after { + color: #eee; +} + +div.dataTables_scrollHead table.dataTable { + margin-bottom: 0 !important; +} + +div.dataTables_scrollBody > table { + border-top: none; + margin-top: 0 !important; + margin-bottom: 0 !important; +} +div.dataTables_scrollBody > table > thead .sorting:after, +div.dataTables_scrollBody > table > thead .sorting_asc:after, +div.dataTables_scrollBody > table > thead .sorting_desc:after { + display: none; +} +div.dataTables_scrollBody > table > tbody > tr:first-child > th, +div.dataTables_scrollBody > table > tbody > tr:first-child > td { + border-top: none; +} + +div.dataTables_scrollFoot > .dataTables_scrollFootInner { + box-sizing: content-box; +} +div.dataTables_scrollFoot > .dataTables_scrollFootInner > table { + margin-top: 0 !important; + border-top: none; +} + +@media screen and (max-width: 767px) { + div.dataTables_wrapper div.dataTables_length, + div.dataTables_wrapper div.dataTables_filter, + div.dataTables_wrapper div.dataTables_info, + div.dataTables_wrapper div.dataTables_paginate { + text-align: center; + } +} +table.dataTable.table-condensed > thead > tr > th { + padding-right: 20px; +} +table.dataTable.table-condensed .sorting:after, +table.dataTable.table-condensed .sorting_asc:after, +table.dataTable.table-condensed .sorting_desc:after { + top: 6px; + right: 6px; +} + +table.table-bordered.dataTable th, +table.table-bordered.dataTable td { + border-left-width: 0; +} +table.table-bordered.dataTable th:last-child, table.table-bordered.dataTable th:last-child, +table.table-bordered.dataTable td:last-child, +table.table-bordered.dataTable td:last-child { + border-right-width: 0; +} +table.table-bordered.dataTable tbody th, +table.table-bordered.dataTable tbody td { + border-bottom-width: 0; +} + +div.dataTables_scrollHead table.table-bordered { + border-bottom-width: 0; +} + +div.table-responsive > div.dataTables_wrapper > div.row { + margin: 0; +} +div.table-responsive > div.dataTables_wrapper > div.row > div[class^="col-"]:first-child { + padding-left: 0; +} +div.table-responsive > div.dataTables_wrapper > div.row > div[class^="col-"]:last-child { + padding-right: 0; +} diff --git a/wwwroot/assets/extra-libs/DataTables/DataTables-1.10.16/css/dataTables.bootstrap.min.css b/wwwroot/assets/extra-libs/DataTables/DataTables-1.10.16/css/dataTables.bootstrap.min.css new file mode 100644 index 0000000..af6ecfe --- /dev/null +++ b/wwwroot/assets/extra-libs/DataTables/DataTables-1.10.16/css/dataTables.bootstrap.min.css @@ -0,0 +1 @@ +table.dataTable{clear:both;margin-top:6px !important;margin-bottom:6px !important;max-width:none !important;border-collapse:separate !important}table.dataTable td,table.dataTable th{-webkit-box-sizing:content-box;box-sizing:content-box}table.dataTable td.dataTables_empty,table.dataTable th.dataTables_empty{text-align:center}table.dataTable.nowrap th,table.dataTable.nowrap td{white-space:nowrap}div.dataTables_wrapper div.dataTables_length label{font-weight:normal;text-align:left;white-space:nowrap}div.dataTables_wrapper div.dataTables_length select{width:75px;display:inline-block}div.dataTables_wrapper div.dataTables_filter{text-align:right}div.dataTables_wrapper div.dataTables_filter label{font-weight:normal;white-space:nowrap;text-align:left}div.dataTables_wrapper div.dataTables_filter input{margin-left:0.5em;display:inline-block;width:auto}div.dataTables_wrapper div.dataTables_info{padding-top:8px;white-space:nowrap}div.dataTables_wrapper div.dataTables_paginate{margin:0;white-space:nowrap;text-align:right}div.dataTables_wrapper div.dataTables_paginate ul.pagination{margin:2px 0;white-space:nowrap}div.dataTables_wrapper div.dataTables_processing{position:absolute;top:50%;left:50%;width:200px;margin-left:-100px;margin-top:-26px;text-align:center;padding:1em 0}table.dataTable thead>tr>th.sorting_asc,table.dataTable thead>tr>th.sorting_desc,table.dataTable thead>tr>th.sorting,table.dataTable thead>tr>td.sorting_asc,table.dataTable thead>tr>td.sorting_desc,table.dataTable thead>tr>td.sorting{padding-right:30px}table.dataTable thead>tr>th:active,table.dataTable thead>tr>td:active{outline:none}table.dataTable thead .sorting,table.dataTable thead .sorting_asc,table.dataTable thead .sorting_desc,table.dataTable thead .sorting_asc_disabled,table.dataTable thead .sorting_desc_disabled{cursor:pointer;position:relative}table.dataTable thead .sorting:after,table.dataTable thead .sorting_asc:after,table.dataTable thead .sorting_desc:after,table.dataTable thead .sorting_asc_disabled:after,table.dataTable thead .sorting_desc_disabled:after{position:absolute;bottom:8px;right:8px;display:block;font-family:'Glyphicons Halflings';opacity:0.5}table.dataTable thead .sorting:after{opacity:0.2;content:"\e150"}table.dataTable thead .sorting_asc:after{content:"\e155"}table.dataTable thead .sorting_desc:after{content:"\e156"}table.dataTable thead .sorting_asc_disabled:after,table.dataTable thead .sorting_desc_disabled:after{color:#eee}div.dataTables_scrollHead table.dataTable{margin-bottom:0 !important}div.dataTables_scrollBody>table{border-top:none;margin-top:0 !important;margin-bottom:0 !important}div.dataTables_scrollBody>table>thead .sorting:after,div.dataTables_scrollBody>table>thead .sorting_asc:after,div.dataTables_scrollBody>table>thead .sorting_desc:after{display:none}div.dataTables_scrollBody>table>tbody>tr:first-child>th,div.dataTables_scrollBody>table>tbody>tr:first-child>td{border-top:none}div.dataTables_scrollFoot>.dataTables_scrollFootInner{box-sizing:content-box}div.dataTables_scrollFoot>.dataTables_scrollFootInner>table{margin-top:0 !important;border-top:none}@media screen and (max-width: 767px){div.dataTables_wrapper div.dataTables_length,div.dataTables_wrapper div.dataTables_filter,div.dataTables_wrapper div.dataTables_info,div.dataTables_wrapper div.dataTables_paginate{text-align:center}}table.dataTable.table-condensed>thead>tr>th{padding-right:20px}table.dataTable.table-condensed .sorting:after,table.dataTable.table-condensed .sorting_asc:after,table.dataTable.table-condensed .sorting_desc:after{top:6px;right:6px}table.table-bordered.dataTable th,table.table-bordered.dataTable td{border-left-width:0}table.table-bordered.dataTable th:last-child,table.table-bordered.dataTable th:last-child,table.table-bordered.dataTable td:last-child,table.table-bordered.dataTable td:last-child{border-right-width:0}table.table-bordered.dataTable tbody th,table.table-bordered.dataTable tbody td{border-bottom-width:0}div.dataTables_scrollHead table.table-bordered{border-bottom-width:0}div.table-responsive>div.dataTables_wrapper>div.row{margin:0}div.table-responsive>div.dataTables_wrapper>div.row>div[class^="col-"]:first-child{padding-left:0}div.table-responsive>div.dataTables_wrapper>div.row>div[class^="col-"]:last-child{padding-right:0} diff --git a/wwwroot/assets/extra-libs/DataTables/DataTables-1.10.16/css/dataTables.bootstrap4.css b/wwwroot/assets/extra-libs/DataTables/DataTables-1.10.16/css/dataTables.bootstrap4.css new file mode 100644 index 0000000..56ea4f4 --- /dev/null +++ b/wwwroot/assets/extra-libs/DataTables/DataTables-1.10.16/css/dataTables.bootstrap4.css @@ -0,0 +1,202 @@ +table.dataTable { + clear: both; + margin-top: 6px !important; + margin-bottom: 6px !important; + max-width: none !important; + border-collapse: separate !important; +} +table.dataTable td, +table.dataTable th { + -webkit-box-sizing: content-box; + box-sizing: content-box; +} +table.dataTable td.dataTables_empty, +table.dataTable th.dataTables_empty { + text-align: center; +} +table.dataTable.nowrap th, +table.dataTable.nowrap td { + white-space: nowrap; +} + +div.dataTables_wrapper div.dataTables_length label { + font-weight: normal; + text-align: left; + white-space: nowrap; +} +div.dataTables_wrapper div.dataTables_length select { + width: 75px; + display: inline-block; +} +div.dataTables_wrapper div.dataTables_filter { + text-align: right; +} +div.dataTables_wrapper div.dataTables_filter label { + font-weight: normal; + white-space: nowrap; + text-align: left; +} +div.dataTables_wrapper div.dataTables_filter input { + margin-left: 0.5em; + display: inline-block; + width: auto; +} +div.dataTables_wrapper div.dataTables_info { + padding-top: 0.85em; + white-space: nowrap; +} +div.dataTables_wrapper div.dataTables_paginate { + margin: 0; + white-space: nowrap; + text-align: right; +} +div.dataTables_wrapper div.dataTables_paginate ul.pagination { + margin: 2px 0; + white-space: nowrap; + justify-content: flex-end; +} +div.dataTables_wrapper div.dataTables_processing { + position: absolute; + top: 50%; + left: 50%; + width: 200px; + margin-left: -100px; + margin-top: -26px; + text-align: center; + padding: 1em 0; +} + +table.dataTable thead > tr > th.sorting_asc, table.dataTable thead > tr > th.sorting_desc, table.dataTable thead > tr > th.sorting, +table.dataTable thead > tr > td.sorting_asc, +table.dataTable thead > tr > td.sorting_desc, +table.dataTable thead > tr > td.sorting { + padding-right: 30px; +} +table.dataTable thead > tr > th:active, +table.dataTable thead > tr > td:active { + outline: none; +} +table.dataTable thead .sorting, +table.dataTable thead .sorting_asc, +table.dataTable thead .sorting_desc, +table.dataTable thead .sorting_asc_disabled, +table.dataTable thead .sorting_desc_disabled { + cursor: pointer; + position: relative; +} +table.dataTable thead .sorting:before, table.dataTable thead .sorting:after, +table.dataTable thead .sorting_asc:before, +table.dataTable thead .sorting_asc:after, +table.dataTable thead .sorting_desc:before, +table.dataTable thead .sorting_desc:after, +table.dataTable thead .sorting_asc_disabled:before, +table.dataTable thead .sorting_asc_disabled:after, +table.dataTable thead .sorting_desc_disabled:before, +table.dataTable thead .sorting_desc_disabled:after { + position: absolute; + bottom: 0.9em; + display: block; + opacity: 0.3; +} +table.dataTable thead .sorting:before, +table.dataTable thead .sorting_asc:before, +table.dataTable thead .sorting_desc:before, +table.dataTable thead .sorting_asc_disabled:before, +table.dataTable thead .sorting_desc_disabled:before { + right: 1em; + content: "\2191"; +} +table.dataTable thead .sorting:after, +table.dataTable thead .sorting_asc:after, +table.dataTable thead .sorting_desc:after, +table.dataTable thead .sorting_asc_disabled:after, +table.dataTable thead .sorting_desc_disabled:after { + right: 0.5em; + content: "\2193"; +} +table.dataTable thead .sorting_asc:before, +table.dataTable thead .sorting_desc:after { + opacity: 1; +} +table.dataTable thead .sorting_asc_disabled:before, +table.dataTable thead .sorting_desc_disabled:after { + opacity: 0; +} + +div.dataTables_scrollHead table.dataTable { + margin-bottom: 0 !important; +} + +div.dataTables_scrollBody table { + border-top: none; + margin-top: 0 !important; + margin-bottom: 0 !important; +} +div.dataTables_scrollBody table thead .sorting:after, +div.dataTables_scrollBody table thead .sorting_asc:after, +div.dataTables_scrollBody table thead .sorting_desc:after { + display: none; +} +div.dataTables_scrollBody table tbody tr:first-child th, +div.dataTables_scrollBody table tbody tr:first-child td { + border-top: none; +} + +div.dataTables_scrollFoot > .dataTables_scrollFootInner { + box-sizing: content-box; +} +div.dataTables_scrollFoot > .dataTables_scrollFootInner > table { + margin-top: 0 !important; + border-top: none; +} + +@media screen and (max-width: 767px) { + div.dataTables_wrapper div.dataTables_length, + div.dataTables_wrapper div.dataTables_filter, + div.dataTables_wrapper div.dataTables_info, + div.dataTables_wrapper div.dataTables_paginate { + text-align: center; + } +} +table.dataTable.table-sm > thead > tr > th { + padding-right: 20px; +} +table.dataTable.table-sm .sorting:before, +table.dataTable.table-sm .sorting_asc:before, +table.dataTable.table-sm .sorting_desc:before { + top: 5px; + right: 0.85em; +} +table.dataTable.table-sm .sorting:after, +table.dataTable.table-sm .sorting_asc:after, +table.dataTable.table-sm .sorting_desc:after { + top: 5px; +} + +table.table-bordered.dataTable th, +table.table-bordered.dataTable td { + border-left-width: 0; +} +table.table-bordered.dataTable th:last-child, table.table-bordered.dataTable th:last-child, +table.table-bordered.dataTable td:last-child, +table.table-bordered.dataTable td:last-child { + border-right-width: 0; +} +table.table-bordered.dataTable tbody th, +table.table-bordered.dataTable tbody td { + border-bottom-width: 0; +} + +div.dataTables_scrollHead table.table-bordered { + border-bottom-width: 0; +} + +div.table-responsive > div.dataTables_wrapper > div.row { + margin: 0; +} +div.table-responsive > div.dataTables_wrapper > div.row > div[class^="col-"]:first-child { + padding-left: 0; +} +div.table-responsive > div.dataTables_wrapper > div.row > div[class^="col-"]:last-child { + padding-right: 0; +} diff --git a/wwwroot/assets/extra-libs/DataTables/DataTables-1.10.16/css/dataTables.bootstrap4.min.css b/wwwroot/assets/extra-libs/DataTables/DataTables-1.10.16/css/dataTables.bootstrap4.min.css new file mode 100644 index 0000000..48b76ed --- /dev/null +++ b/wwwroot/assets/extra-libs/DataTables/DataTables-1.10.16/css/dataTables.bootstrap4.min.css @@ -0,0 +1 @@ +table.dataTable{clear:both;margin-top:6px !important;margin-bottom:6px !important;max-width:none !important;border-collapse:separate !important}table.dataTable td,table.dataTable th{-webkit-box-sizing:content-box;box-sizing:content-box}table.dataTable td.dataTables_empty,table.dataTable th.dataTables_empty{text-align:center}table.dataTable.nowrap th,table.dataTable.nowrap td{white-space:nowrap}div.dataTables_wrapper div.dataTables_length label{font-weight:normal;text-align:left;white-space:nowrap}div.dataTables_wrapper div.dataTables_length select{width:75px;display:inline-block}div.dataTables_wrapper div.dataTables_filter{text-align:right}div.dataTables_wrapper div.dataTables_filter label{font-weight:normal;white-space:nowrap;text-align:left}div.dataTables_wrapper div.dataTables_filter input{margin-left:0.5em;display:inline-block;width:auto}div.dataTables_wrapper div.dataTables_info{padding-top:0.85em;white-space:nowrap}div.dataTables_wrapper div.dataTables_paginate{margin:0;white-space:nowrap;text-align:right}div.dataTables_wrapper div.dataTables_paginate ul.pagination{margin:2px 0;white-space:nowrap;justify-content:flex-end}div.dataTables_wrapper div.dataTables_processing{position:absolute;top:50%;left:50%;width:200px;margin-left:-100px;margin-top:-26px;text-align:center;padding:1em 0}table.dataTable thead>tr>th.sorting_asc,table.dataTable thead>tr>th.sorting_desc,table.dataTable thead>tr>th.sorting,table.dataTable thead>tr>td.sorting_asc,table.dataTable thead>tr>td.sorting_desc,table.dataTable thead>tr>td.sorting{padding-right:30px}table.dataTable thead>tr>th:active,table.dataTable thead>tr>td:active{outline:none}table.dataTable thead .sorting,table.dataTable thead .sorting_asc,table.dataTable thead .sorting_desc,table.dataTable thead .sorting_asc_disabled,table.dataTable thead .sorting_desc_disabled{cursor:pointer;position:relative}table.dataTable thead .sorting:before,table.dataTable thead .sorting:after,table.dataTable thead .sorting_asc:before,table.dataTable thead .sorting_asc:after,table.dataTable thead .sorting_desc:before,table.dataTable thead .sorting_desc:after,table.dataTable thead .sorting_asc_disabled:before,table.dataTable thead .sorting_asc_disabled:after,table.dataTable thead .sorting_desc_disabled:before,table.dataTable thead .sorting_desc_disabled:after{position:absolute;bottom:0.9em;display:block;opacity:0.3}table.dataTable thead .sorting:before,table.dataTable thead .sorting_asc:before,table.dataTable thead .sorting_desc:before,table.dataTable thead .sorting_asc_disabled:before,table.dataTable thead .sorting_desc_disabled:before{right:1em;content:"\2191"}table.dataTable thead .sorting:after,table.dataTable thead .sorting_asc:after,table.dataTable thead .sorting_desc:after,table.dataTable thead .sorting_asc_disabled:after,table.dataTable thead .sorting_desc_disabled:after{right:0.5em;content:"\2193"}table.dataTable thead .sorting_asc:before,table.dataTable thead .sorting_desc:after{opacity:1}table.dataTable thead .sorting_asc_disabled:before,table.dataTable thead .sorting_desc_disabled:after{opacity:0}div.dataTables_scrollHead table.dataTable{margin-bottom:0 !important}div.dataTables_scrollBody table{border-top:none;margin-top:0 !important;margin-bottom:0 !important}div.dataTables_scrollBody table thead .sorting:after,div.dataTables_scrollBody table thead .sorting_asc:after,div.dataTables_scrollBody table thead .sorting_desc:after{display:none}div.dataTables_scrollBody table tbody tr:first-child th,div.dataTables_scrollBody table tbody tr:first-child td{border-top:none}div.dataTables_scrollFoot>.dataTables_scrollFootInner{box-sizing:content-box}div.dataTables_scrollFoot>.dataTables_scrollFootInner>table{margin-top:0 !important;border-top:none}@media screen and (max-width: 767px){div.dataTables_wrapper div.dataTables_length,div.dataTables_wrapper div.dataTables_filter,div.dataTables_wrapper div.dataTables_info,div.dataTables_wrapper div.dataTables_paginate{text-align:center}}table.dataTable.table-sm>thead>tr>th{padding-right:20px}table.dataTable.table-sm .sorting:before,table.dataTable.table-sm .sorting_asc:before,table.dataTable.table-sm .sorting_desc:before{top:5px;right:0.85em}table.dataTable.table-sm .sorting:after,table.dataTable.table-sm .sorting_asc:after,table.dataTable.table-sm .sorting_desc:after{top:5px}table.table-bordered.dataTable th,table.table-bordered.dataTable td{border-left-width:0}table.table-bordered.dataTable th:last-child,table.table-bordered.dataTable th:last-child,table.table-bordered.dataTable td:last-child,table.table-bordered.dataTable td:last-child{border-right-width:0}table.table-bordered.dataTable tbody th,table.table-bordered.dataTable tbody td{border-bottom-width:0}div.dataTables_scrollHead table.table-bordered{border-bottom-width:0}div.table-responsive>div.dataTables_wrapper>div.row{margin:0}div.table-responsive>div.dataTables_wrapper>div.row>div[class^="col-"]:first-child{padding-left:0}div.table-responsive>div.dataTables_wrapper>div.row>div[class^="col-"]:last-child{padding-right:0} diff --git a/wwwroot/assets/extra-libs/DataTables/DataTables-1.10.16/css/dataTables.foundation.css b/wwwroot/assets/extra-libs/DataTables/DataTables-1.10.16/css/dataTables.foundation.css new file mode 100644 index 0000000..79848c9 --- /dev/null +++ b/wwwroot/assets/extra-libs/DataTables/DataTables-1.10.16/css/dataTables.foundation.css @@ -0,0 +1,118 @@ +table.dataTable { + clear: both; + margin: 0.5em 0 !important; + max-width: none !important; + width: 100%; +} +table.dataTable td, +table.dataTable th { + -webkit-box-sizing: content-box; + box-sizing: content-box; +} +table.dataTable td.dataTables_empty, +table.dataTable th.dataTables_empty { + text-align: center; +} +table.dataTable.nowrap th, table.dataTable.nowrap td { + white-space: nowrap; +} + +div.dataTables_wrapper { + position: relative; +} +div.dataTables_wrapper div.dataTables_length label { + float: left; + text-align: left; + margin-bottom: 0; +} +div.dataTables_wrapper div.dataTables_length select { + width: 75px; + margin-bottom: 0; +} +div.dataTables_wrapper div.dataTables_filter label { + float: right; + margin-bottom: 0; +} +div.dataTables_wrapper div.dataTables_filter input { + display: inline-block !important; + width: auto !important; + margin-bottom: 0; + margin-left: 0.5em; +} +div.dataTables_wrapper div.dataTables_info { + padding-top: 2px; +} +div.dataTables_wrapper div.dataTables_paginate { + float: right; + margin: 0; +} +div.dataTables_wrapper div.dataTables_processing { + position: absolute; + top: 50%; + left: 50%; + width: 200px; + margin-left: -100px; + margin-top: -26px; + text-align: center; + padding: 1rem 0; +} + +table.dataTable thead > tr > th.sorting_asc, table.dataTable thead > tr > th.sorting_desc, table.dataTable thead > tr > th.sorting, +table.dataTable thead > tr > td.sorting_asc, +table.dataTable thead > tr > td.sorting_desc, +table.dataTable thead > tr > td.sorting { + padding-right: 1.5rem; +} +table.dataTable thead > tr > th:active, +table.dataTable thead > tr > td:active { + outline: none; +} +table.dataTable thead .sorting, +table.dataTable thead .sorting_asc, +table.dataTable thead .sorting_desc, +table.dataTable thead .sorting_asc_disabled, +table.dataTable thead .sorting_desc_disabled { + cursor: pointer; +} +table.dataTable thead .sorting, +table.dataTable thead .sorting_asc, +table.dataTable thead .sorting_desc, +table.dataTable thead .sorting_asc_disabled, +table.dataTable thead .sorting_desc_disabled { + background-repeat: no-repeat; + background-position: center right; +} +table.dataTable thead .sorting { + background-image: url("../images/sort_both.png"); +} +table.dataTable thead .sorting_asc { + background-image: url("../images/sort_asc.png"); +} +table.dataTable thead .sorting_desc { + background-image: url("../images/sort_desc.png"); +} +table.dataTable thead .sorting_asc_disabled { + background-image: url("../images/sort_asc_disabled.png"); +} +table.dataTable thead .sorting_desc_disabled { + background-image: url("../images/sort_desc_disabled.png"); +} + +div.dataTables_scrollHead table { + margin-bottom: 0 !important; +} + +div.dataTables_scrollBody table { + border-top: none; + margin-top: 0 !important; + margin-bottom: 0 !important; +} +div.dataTables_scrollBody table tbody tr:first-child th, +div.dataTables_scrollBody table tbody tr:first-child td { + border-top: none; +} + +div.dataTables_scrollFoot table { + margin-top: 0 !important; + border-top: none; +} diff --git a/wwwroot/assets/extra-libs/DataTables/DataTables-1.10.16/css/dataTables.foundation.min.css b/wwwroot/assets/extra-libs/DataTables/DataTables-1.10.16/css/dataTables.foundation.min.css new file mode 100644 index 0000000..73af41e --- /dev/null +++ b/wwwroot/assets/extra-libs/DataTables/DataTables-1.10.16/css/dataTables.foundation.min.css @@ -0,0 +1 @@ +table.dataTable{clear:both;margin:0.5em 0 !important;max-width:none !important;width:100%}table.dataTable td,table.dataTable th{-webkit-box-sizing:content-box;box-sizing:content-box}table.dataTable td.dataTables_empty,table.dataTable th.dataTables_empty{text-align:center}table.dataTable.nowrap th,table.dataTable.nowrap td{white-space:nowrap}div.dataTables_wrapper{position:relative}div.dataTables_wrapper div.dataTables_length label{float:left;text-align:left;margin-bottom:0}div.dataTables_wrapper div.dataTables_length select{width:75px;margin-bottom:0}div.dataTables_wrapper div.dataTables_filter label{float:right;margin-bottom:0}div.dataTables_wrapper div.dataTables_filter input{display:inline-block !important;width:auto !important;margin-bottom:0;margin-left:0.5em}div.dataTables_wrapper div.dataTables_info{padding-top:2px}div.dataTables_wrapper div.dataTables_paginate{float:right;margin:0}div.dataTables_wrapper div.dataTables_processing{position:absolute;top:50%;left:50%;width:200px;margin-left:-100px;margin-top:-26px;text-align:center;padding:1rem 0}table.dataTable thead>tr>th.sorting_asc,table.dataTable thead>tr>th.sorting_desc,table.dataTable thead>tr>th.sorting,table.dataTable thead>tr>td.sorting_asc,table.dataTable thead>tr>td.sorting_desc,table.dataTable thead>tr>td.sorting{padding-right:1.5rem}table.dataTable thead>tr>th:active,table.dataTable thead>tr>td:active{outline:none}table.dataTable thead .sorting,table.dataTable thead .sorting_asc,table.dataTable thead .sorting_desc,table.dataTable thead .sorting_asc_disabled,table.dataTable thead .sorting_desc_disabled{cursor:pointer}table.dataTable thead .sorting,table.dataTable thead .sorting_asc,table.dataTable thead .sorting_desc,table.dataTable thead .sorting_asc_disabled,table.dataTable thead .sorting_desc_disabled{background-repeat:no-repeat;background-position:center right}table.dataTable thead .sorting{background-image:url("../images/sort_both.png")}table.dataTable thead .sorting_asc{background-image:url("../images/sort_asc.png")}table.dataTable thead .sorting_desc{background-image:url("../images/sort_desc.png")}table.dataTable thead .sorting_asc_disabled{background-image:url("../images/sort_asc_disabled.png")}table.dataTable thead .sorting_desc_disabled{background-image:url("../images/sort_desc_disabled.png")}div.dataTables_scrollHead table{margin-bottom:0 !important}div.dataTables_scrollBody table{border-top:none;margin-top:0 !important;margin-bottom:0 !important}div.dataTables_scrollBody table tbody tr:first-child th,div.dataTables_scrollBody table tbody tr:first-child td{border-top:none}div.dataTables_scrollFoot table{margin-top:0 !important;border-top:none} diff --git a/wwwroot/assets/extra-libs/DataTables/DataTables-1.10.16/css/dataTables.jqueryui.css b/wwwroot/assets/extra-libs/DataTables/DataTables-1.10.16/css/dataTables.jqueryui.css new file mode 100644 index 0000000..5070b04 --- /dev/null +++ b/wwwroot/assets/extra-libs/DataTables/DataTables-1.10.16/css/dataTables.jqueryui.css @@ -0,0 +1,481 @@ +/* + * Table styles + */ +table.dataTable { + width: 100%; + margin: 0 auto; + clear: both; + border-collapse: separate; + border-spacing: 0; + /* + * Header and footer styles + */ + /* + * Body styles + */ +} +table.dataTable thead th, +table.dataTable tfoot th { + font-weight: bold; +} +table.dataTable thead th, +table.dataTable thead td { + padding: 10px 18px; +} +table.dataTable thead th:active, +table.dataTable thead td:active { + outline: none; +} +table.dataTable tfoot th, +table.dataTable tfoot td { + padding: 10px 18px 6px 18px; +} +table.dataTable tbody tr { + background-color: #ffffff; +} +table.dataTable tbody tr.selected { + background-color: #B0BED9; +} +table.dataTable tbody th, +table.dataTable tbody td { + padding: 8px 10px; +} +table.dataTable.row-border tbody th, table.dataTable.row-border tbody td, table.dataTable.display tbody th, table.dataTable.display tbody td { + border-top: 1px solid #ddd; +} +table.dataTable.row-border tbody tr:first-child th, +table.dataTable.row-border tbody tr:first-child td, table.dataTable.display tbody tr:first-child th, +table.dataTable.display tbody tr:first-child td { + border-top: none; +} +table.dataTable.cell-border tbody th, table.dataTable.cell-border tbody td { + border-top: 1px solid #ddd; + border-right: 1px solid #ddd; +} +table.dataTable.cell-border tbody tr th:first-child, +table.dataTable.cell-border tbody tr td:first-child { + border-left: 1px solid #ddd; +} +table.dataTable.cell-border tbody tr:first-child th, +table.dataTable.cell-border tbody tr:first-child td { + border-top: none; +} +table.dataTable.stripe tbody tr.odd, table.dataTable.display tbody tr.odd { + background-color: #f9f9f9; +} +table.dataTable.stripe tbody tr.odd.selected, table.dataTable.display tbody tr.odd.selected { + background-color: #acbad4; +} +table.dataTable.hover tbody tr:hover, table.dataTable.display tbody tr:hover { + background-color: #f6f6f6; +} +table.dataTable.hover tbody tr:hover.selected, table.dataTable.display tbody tr:hover.selected { + background-color: #aab7d1; +} +table.dataTable.order-column tbody tr > .sorting_1, +table.dataTable.order-column tbody tr > .sorting_2, +table.dataTable.order-column tbody tr > .sorting_3, table.dataTable.display tbody tr > .sorting_1, +table.dataTable.display tbody tr > .sorting_2, +table.dataTable.display tbody tr > .sorting_3 { + background-color: #fafafa; +} +table.dataTable.order-column tbody tr.selected > .sorting_1, +table.dataTable.order-column tbody tr.selected > .sorting_2, +table.dataTable.order-column tbody tr.selected > .sorting_3, table.dataTable.display tbody tr.selected > .sorting_1, +table.dataTable.display tbody tr.selected > .sorting_2, +table.dataTable.display tbody tr.selected > .sorting_3 { + background-color: #acbad5; +} +table.dataTable.display tbody tr.odd > .sorting_1, table.dataTable.order-column.stripe tbody tr.odd > .sorting_1 { + background-color: #f1f1f1; +} +table.dataTable.display tbody tr.odd > .sorting_2, table.dataTable.order-column.stripe tbody tr.odd > .sorting_2 { + background-color: #f3f3f3; +} +table.dataTable.display tbody tr.odd > .sorting_3, table.dataTable.order-column.stripe tbody tr.odd > .sorting_3 { + background-color: whitesmoke; +} +table.dataTable.display tbody tr.odd.selected > .sorting_1, table.dataTable.order-column.stripe tbody tr.odd.selected > .sorting_1 { + background-color: #a6b4cd; +} +table.dataTable.display tbody tr.odd.selected > .sorting_2, table.dataTable.order-column.stripe tbody tr.odd.selected > .sorting_2 { + background-color: #a8b5cf; +} +table.dataTable.display tbody tr.odd.selected > .sorting_3, table.dataTable.order-column.stripe tbody tr.odd.selected > .sorting_3 { + background-color: #a9b7d1; +} +table.dataTable.display tbody tr.even > .sorting_1, table.dataTable.order-column.stripe tbody tr.even > .sorting_1 { + background-color: #fafafa; +} +table.dataTable.display tbody tr.even > .sorting_2, table.dataTable.order-column.stripe tbody tr.even > .sorting_2 { + background-color: #fcfcfc; +} +table.dataTable.display tbody tr.even > .sorting_3, table.dataTable.order-column.stripe tbody tr.even > .sorting_3 { + background-color: #fefefe; +} +table.dataTable.display tbody tr.even.selected > .sorting_1, table.dataTable.order-column.stripe tbody tr.even.selected > .sorting_1 { + background-color: #acbad5; +} +table.dataTable.display tbody tr.even.selected > .sorting_2, table.dataTable.order-column.stripe tbody tr.even.selected > .sorting_2 { + background-color: #aebcd6; +} +table.dataTable.display tbody tr.even.selected > .sorting_3, table.dataTable.order-column.stripe tbody tr.even.selected > .sorting_3 { + background-color: #afbdd8; +} +table.dataTable.display tbody tr:hover > .sorting_1, table.dataTable.order-column.hover tbody tr:hover > .sorting_1 { + background-color: #eaeaea; +} +table.dataTable.display tbody tr:hover > .sorting_2, table.dataTable.order-column.hover tbody tr:hover > .sorting_2 { + background-color: #ececec; +} +table.dataTable.display tbody tr:hover > .sorting_3, table.dataTable.order-column.hover tbody tr:hover > .sorting_3 { + background-color: #efefef; +} +table.dataTable.display tbody tr:hover.selected > .sorting_1, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_1 { + background-color: #a2aec7; +} +table.dataTable.display tbody tr:hover.selected > .sorting_2, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_2 { + background-color: #a3b0c9; +} +table.dataTable.display tbody tr:hover.selected > .sorting_3, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_3 { + background-color: #a5b2cb; +} +table.dataTable.no-footer { + border-bottom: 1px solid #111; +} +table.dataTable.nowrap th, table.dataTable.nowrap td { + white-space: nowrap; +} +table.dataTable.compact thead th, +table.dataTable.compact thead td { + padding: 4px 17px 4px 4px; +} +table.dataTable.compact tfoot th, +table.dataTable.compact tfoot td { + padding: 4px; +} +table.dataTable.compact tbody th, +table.dataTable.compact tbody td { + padding: 4px; +} +table.dataTable th.dt-left, +table.dataTable td.dt-left { + text-align: left; +} +table.dataTable th.dt-center, +table.dataTable td.dt-center, +table.dataTable td.dataTables_empty { + text-align: center; +} +table.dataTable th.dt-right, +table.dataTable td.dt-right { + text-align: right; +} +table.dataTable th.dt-justify, +table.dataTable td.dt-justify { + text-align: justify; +} +table.dataTable th.dt-nowrap, +table.dataTable td.dt-nowrap { + white-space: nowrap; +} +table.dataTable thead th.dt-head-left, +table.dataTable thead td.dt-head-left, +table.dataTable tfoot th.dt-head-left, +table.dataTable tfoot td.dt-head-left { + text-align: left; +} +table.dataTable thead th.dt-head-center, +table.dataTable thead td.dt-head-center, +table.dataTable tfoot th.dt-head-center, +table.dataTable tfoot td.dt-head-center { + text-align: center; +} +table.dataTable thead th.dt-head-right, +table.dataTable thead td.dt-head-right, +table.dataTable tfoot th.dt-head-right, +table.dataTable tfoot td.dt-head-right { + text-align: right; +} +table.dataTable thead th.dt-head-justify, +table.dataTable thead td.dt-head-justify, +table.dataTable tfoot th.dt-head-justify, +table.dataTable tfoot td.dt-head-justify { + text-align: justify; +} +table.dataTable thead th.dt-head-nowrap, +table.dataTable thead td.dt-head-nowrap, +table.dataTable tfoot th.dt-head-nowrap, +table.dataTable tfoot td.dt-head-nowrap { + white-space: nowrap; +} +table.dataTable tbody th.dt-body-left, +table.dataTable tbody td.dt-body-left { + text-align: left; +} +table.dataTable tbody th.dt-body-center, +table.dataTable tbody td.dt-body-center { + text-align: center; +} +table.dataTable tbody th.dt-body-right, +table.dataTable tbody td.dt-body-right { + text-align: right; +} +table.dataTable tbody th.dt-body-justify, +table.dataTable tbody td.dt-body-justify { + text-align: justify; +} +table.dataTable tbody th.dt-body-nowrap, +table.dataTable tbody td.dt-body-nowrap { + white-space: nowrap; +} + +table.dataTable, +table.dataTable th, +table.dataTable td { + box-sizing: content-box; +} + +/* + * Control feature layout + */ +.dataTables_wrapper { + position: relative; + clear: both; + *zoom: 1; + zoom: 1; +} +.dataTables_wrapper .dataTables_length { + float: left; +} +.dataTables_wrapper .dataTables_filter { + float: right; + text-align: right; +} +.dataTables_wrapper .dataTables_filter input { + margin-left: 0.5em; +} +.dataTables_wrapper .dataTables_info { + clear: both; + float: left; + padding-top: 0.755em; +} +.dataTables_wrapper .dataTables_paginate { + float: right; + text-align: right; + padding-top: 0.25em; +} +.dataTables_wrapper .dataTables_paginate .paginate_button { + box-sizing: border-box; + display: inline-block; + min-width: 1.5em; + padding: 0.5em 1em; + margin-left: 2px; + text-align: center; + text-decoration: none !important; + cursor: pointer; + *cursor: hand; + color: #333 !important; + border: 1px solid transparent; + border-radius: 2px; +} +.dataTables_wrapper .dataTables_paginate .paginate_button.current, .dataTables_wrapper .dataTables_paginate .paginate_button.current:hover { + color: #333 !important; + border: 1px solid #979797; + background-color: white; + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, white), color-stop(100%, #dcdcdc)); + /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(top, white 0%, #dcdcdc 100%); + /* Chrome10+,Safari5.1+ */ + background: -moz-linear-gradient(top, white 0%, #dcdcdc 100%); + /* FF3.6+ */ + background: -ms-linear-gradient(top, white 0%, #dcdcdc 100%); + /* IE10+ */ + background: -o-linear-gradient(top, white 0%, #dcdcdc 100%); + /* Opera 11.10+ */ + background: linear-gradient(to bottom, white 0%, #dcdcdc 100%); + /* W3C */ +} +.dataTables_wrapper .dataTables_paginate .paginate_button.disabled, .dataTables_wrapper .dataTables_paginate .paginate_button.disabled:hover, .dataTables_wrapper .dataTables_paginate .paginate_button.disabled:active { + cursor: default; + color: #666 !important; + border: 1px solid transparent; + background: transparent; + box-shadow: none; +} +.dataTables_wrapper .dataTables_paginate .paginate_button:hover { + color: white !important; + border: 1px solid #111; + background-color: #585858; + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #585858), color-stop(100%, #111)); + /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(top, #585858 0%, #111 100%); + /* Chrome10+,Safari5.1+ */ + background: -moz-linear-gradient(top, #585858 0%, #111 100%); + /* FF3.6+ */ + background: -ms-linear-gradient(top, #585858 0%, #111 100%); + /* IE10+ */ + background: -o-linear-gradient(top, #585858 0%, #111 100%); + /* Opera 11.10+ */ + background: linear-gradient(to bottom, #585858 0%, #111 100%); + /* W3C */ +} +.dataTables_wrapper .dataTables_paginate .paginate_button:active { + outline: none; + background-color: #2b2b2b; + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #2b2b2b), color-stop(100%, #0c0c0c)); + /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); + /* Chrome10+,Safari5.1+ */ + background: -moz-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); + /* FF3.6+ */ + background: -ms-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); + /* IE10+ */ + background: -o-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); + /* Opera 11.10+ */ + background: linear-gradient(to bottom, #2b2b2b 0%, #0c0c0c 100%); + /* W3C */ + box-shadow: inset 0 0 3px #111; +} +.dataTables_wrapper .dataTables_paginate .ellipsis { + padding: 0 1em; +} +.dataTables_wrapper .dataTables_processing { + position: absolute; + top: 50%; + left: 50%; + width: 100%; + height: 40px; + margin-left: -50%; + margin-top: -25px; + padding-top: 20px; + text-align: center; + font-size: 1.2em; + background-color: white; + background: -webkit-gradient(linear, left top, right top, color-stop(0%, rgba(255, 255, 255, 0)), color-stop(25%, rgba(255, 255, 255, 0.9)), color-stop(75%, rgba(255, 255, 255, 0.9)), color-stop(100%, rgba(255, 255, 255, 0))); + background: -webkit-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%); + background: -moz-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%); + background: -ms-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%); + background: -o-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%); + background: linear-gradient(to right, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%); +} +.dataTables_wrapper .dataTables_length, +.dataTables_wrapper .dataTables_filter, +.dataTables_wrapper .dataTables_info, +.dataTables_wrapper .dataTables_processing, +.dataTables_wrapper .dataTables_paginate { + color: #333; +} +.dataTables_wrapper .dataTables_scroll { + clear: both; +} +.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody { + *margin-top: -1px; + -webkit-overflow-scrolling: touch; +} +.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > th, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > td, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > th, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > td { + vertical-align: middle; +} +.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > th > div.dataTables_sizing, +.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > td > div.dataTables_sizing, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > th > div.dataTables_sizing, +.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > td > div.dataTables_sizing { + height: 0; + overflow: hidden; + margin: 0 !important; + padding: 0 !important; +} +.dataTables_wrapper.no-footer .dataTables_scrollBody { + border-bottom: 1px solid #111; +} +.dataTables_wrapper.no-footer div.dataTables_scrollHead table.dataTable, +.dataTables_wrapper.no-footer div.dataTables_scrollBody > table { + border-bottom: none; +} +.dataTables_wrapper:after { + visibility: hidden; + display: block; + content: ""; + clear: both; + height: 0; +} + +@media screen and (max-width: 767px) { + .dataTables_wrapper .dataTables_info, + .dataTables_wrapper .dataTables_paginate { + float: none; + text-align: center; + } + .dataTables_wrapper .dataTables_paginate { + margin-top: 0.5em; + } +} +@media screen and (max-width: 640px) { + .dataTables_wrapper .dataTables_length, + .dataTables_wrapper .dataTables_filter { + float: none; + text-align: center; + } + .dataTables_wrapper .dataTables_filter { + margin-top: 0.5em; + } +} +table.dataTable thead th div.DataTables_sort_wrapper { + position: relative; +} +table.dataTable thead th div.DataTables_sort_wrapper span { + position: absolute; + top: 50%; + margin-top: -8px; + right: -18px; +} +table.dataTable thead th.ui-state-default, +table.dataTable tfoot th.ui-state-default { + border-left-width: 0; +} +table.dataTable thead th.ui-state-default:first-child, +table.dataTable tfoot th.ui-state-default:first-child { + border-left-width: 1px; +} + +/* + * Control feature layout + */ +.dataTables_wrapper .dataTables_paginate .fg-button { + box-sizing: border-box; + display: inline-block; + min-width: 1.5em; + padding: 0.5em; + margin-left: 2px; + text-align: center; + text-decoration: none !important; + cursor: pointer; + *cursor: hand; + border: 1px solid transparent; +} +.dataTables_wrapper .dataTables_paginate .fg-button:active { + outline: none; +} +.dataTables_wrapper .dataTables_paginate .fg-button:first-child { + border-top-left-radius: 3px; + border-bottom-left-radius: 3px; +} +.dataTables_wrapper .dataTables_paginate .fg-button:last-child { + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; +} +.dataTables_wrapper .ui-widget-header { + font-weight: normal; +} +.dataTables_wrapper .ui-toolbar { + padding: 8px; +} +.dataTables_wrapper.no-footer .dataTables_scrollBody { + border-bottom: none; +} +.dataTables_wrapper .dataTables_length, +.dataTables_wrapper .dataTables_filter, +.dataTables_wrapper .dataTables_info, +.dataTables_wrapper .dataTables_processing, +.dataTables_wrapper .dataTables_paginate { + color: inherit; +} diff --git a/wwwroot/assets/extra-libs/DataTables/DataTables-1.10.16/css/dataTables.jqueryui.min.css b/wwwroot/assets/extra-libs/DataTables/DataTables-1.10.16/css/dataTables.jqueryui.min.css new file mode 100644 index 0000000..4e99c26 --- /dev/null +++ b/wwwroot/assets/extra-libs/DataTables/DataTables-1.10.16/css/dataTables.jqueryui.min.css @@ -0,0 +1 @@ +table.dataTable{width:100%;margin:0 auto;clear:both;border-collapse:separate;border-spacing:0}table.dataTable thead th,table.dataTable tfoot th{font-weight:bold}table.dataTable thead th,table.dataTable thead td{padding:10px 18px}table.dataTable thead th:active,table.dataTable thead td:active{outline:none}table.dataTable tfoot th,table.dataTable tfoot td{padding:10px 18px 6px 18px}table.dataTable tbody tr{background-color:#ffffff}table.dataTable tbody tr.selected{background-color:#B0BED9}table.dataTable tbody th,table.dataTable tbody td{padding:8px 10px}table.dataTable.row-border tbody th,table.dataTable.row-border tbody td,table.dataTable.display tbody th,table.dataTable.display tbody td{border-top:1px solid #ddd}table.dataTable.row-border tbody tr:first-child th,table.dataTable.row-border tbody tr:first-child td,table.dataTable.display tbody tr:first-child th,table.dataTable.display tbody tr:first-child td{border-top:none}table.dataTable.cell-border tbody th,table.dataTable.cell-border tbody td{border-top:1px solid #ddd;border-right:1px solid #ddd}table.dataTable.cell-border tbody tr th:first-child,table.dataTable.cell-border tbody tr td:first-child{border-left:1px solid #ddd}table.dataTable.cell-border tbody tr:first-child th,table.dataTable.cell-border tbody tr:first-child td{border-top:none}table.dataTable.stripe tbody tr.odd,table.dataTable.display tbody tr.odd{background-color:#f9f9f9}table.dataTable.stripe tbody tr.odd.selected,table.dataTable.display tbody tr.odd.selected{background-color:#acbad4}table.dataTable.hover tbody tr:hover,table.dataTable.display tbody tr:hover{background-color:#f6f6f6}table.dataTable.hover tbody tr:hover.selected,table.dataTable.display tbody tr:hover.selected{background-color:#aab7d1}table.dataTable.order-column tbody tr>.sorting_1,table.dataTable.order-column tbody tr>.sorting_2,table.dataTable.order-column tbody tr>.sorting_3,table.dataTable.display tbody tr>.sorting_1,table.dataTable.display tbody tr>.sorting_2,table.dataTable.display tbody tr>.sorting_3{background-color:#fafafa}table.dataTable.order-column tbody tr.selected>.sorting_1,table.dataTable.order-column tbody tr.selected>.sorting_2,table.dataTable.order-column tbody tr.selected>.sorting_3,table.dataTable.display tbody tr.selected>.sorting_1,table.dataTable.display tbody tr.selected>.sorting_2,table.dataTable.display tbody tr.selected>.sorting_3{background-color:#acbad5}table.dataTable.display tbody tr.odd>.sorting_1,table.dataTable.order-column.stripe tbody tr.odd>.sorting_1{background-color:#f1f1f1}table.dataTable.display tbody tr.odd>.sorting_2,table.dataTable.order-column.stripe tbody tr.odd>.sorting_2{background-color:#f3f3f3}table.dataTable.display tbody tr.odd>.sorting_3,table.dataTable.order-column.stripe tbody tr.odd>.sorting_3{background-color:whitesmoke}table.dataTable.display tbody tr.odd.selected>.sorting_1,table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_1{background-color:#a6b4cd}table.dataTable.display tbody tr.odd.selected>.sorting_2,table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_2{background-color:#a8b5cf}table.dataTable.display tbody tr.odd.selected>.sorting_3,table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_3{background-color:#a9b7d1}table.dataTable.display tbody tr.even>.sorting_1,table.dataTable.order-column.stripe tbody tr.even>.sorting_1{background-color:#fafafa}table.dataTable.display tbody tr.even>.sorting_2,table.dataTable.order-column.stripe tbody tr.even>.sorting_2{background-color:#fcfcfc}table.dataTable.display tbody tr.even>.sorting_3,table.dataTable.order-column.stripe tbody tr.even>.sorting_3{background-color:#fefefe}table.dataTable.display tbody tr.even.selected>.sorting_1,table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_1{background-color:#acbad5}table.dataTable.display tbody tr.even.selected>.sorting_2,table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_2{background-color:#aebcd6}table.dataTable.display tbody tr.even.selected>.sorting_3,table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_3{background-color:#afbdd8}table.dataTable.display tbody tr:hover>.sorting_1,table.dataTable.order-column.hover tbody tr:hover>.sorting_1{background-color:#eaeaea}table.dataTable.display tbody tr:hover>.sorting_2,table.dataTable.order-column.hover tbody tr:hover>.sorting_2{background-color:#ececec}table.dataTable.display tbody tr:hover>.sorting_3,table.dataTable.order-column.hover tbody tr:hover>.sorting_3{background-color:#efefef}table.dataTable.display tbody tr:hover.selected>.sorting_1,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_1{background-color:#a2aec7}table.dataTable.display tbody tr:hover.selected>.sorting_2,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_2{background-color:#a3b0c9}table.dataTable.display tbody tr:hover.selected>.sorting_3,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_3{background-color:#a5b2cb}table.dataTable.no-footer{border-bottom:1px solid #111}table.dataTable.nowrap th,table.dataTable.nowrap td{white-space:nowrap}table.dataTable.compact thead th,table.dataTable.compact thead td{padding:4px 17px 4px 4px}table.dataTable.compact tfoot th,table.dataTable.compact tfoot td{padding:4px}table.dataTable.compact tbody th,table.dataTable.compact tbody td{padding:4px}table.dataTable th.dt-left,table.dataTable td.dt-left{text-align:left}table.dataTable th.dt-center,table.dataTable td.dt-center,table.dataTable td.dataTables_empty{text-align:center}table.dataTable th.dt-right,table.dataTable td.dt-right{text-align:right}table.dataTable th.dt-justify,table.dataTable td.dt-justify{text-align:justify}table.dataTable th.dt-nowrap,table.dataTable td.dt-nowrap{white-space:nowrap}table.dataTable thead th.dt-head-left,table.dataTable thead td.dt-head-left,table.dataTable tfoot th.dt-head-left,table.dataTable tfoot td.dt-head-left{text-align:left}table.dataTable thead th.dt-head-center,table.dataTable thead td.dt-head-center,table.dataTable tfoot th.dt-head-center,table.dataTable tfoot td.dt-head-center{text-align:center}table.dataTable thead th.dt-head-right,table.dataTable thead td.dt-head-right,table.dataTable tfoot th.dt-head-right,table.dataTable tfoot td.dt-head-right{text-align:right}table.dataTable thead th.dt-head-justify,table.dataTable thead td.dt-head-justify,table.dataTable tfoot th.dt-head-justify,table.dataTable tfoot td.dt-head-justify{text-align:justify}table.dataTable thead th.dt-head-nowrap,table.dataTable thead td.dt-head-nowrap,table.dataTable tfoot th.dt-head-nowrap,table.dataTable tfoot td.dt-head-nowrap{white-space:nowrap}table.dataTable tbody th.dt-body-left,table.dataTable tbody td.dt-body-left{text-align:left}table.dataTable tbody th.dt-body-center,table.dataTable tbody td.dt-body-center{text-align:center}table.dataTable tbody th.dt-body-right,table.dataTable tbody td.dt-body-right{text-align:right}table.dataTable tbody th.dt-body-justify,table.dataTable tbody td.dt-body-justify{text-align:justify}table.dataTable tbody th.dt-body-nowrap,table.dataTable tbody td.dt-body-nowrap{white-space:nowrap}table.dataTable,table.dataTable th,table.dataTable td{box-sizing:content-box}.dataTables_wrapper{position:relative;clear:both;*zoom:1;zoom:1}.dataTables_wrapper .dataTables_length{float:left}.dataTables_wrapper .dataTables_filter{float:right;text-align:right}.dataTables_wrapper .dataTables_filter input{margin-left:0.5em}.dataTables_wrapper .dataTables_info{clear:both;float:left;padding-top:0.755em}.dataTables_wrapper .dataTables_paginate{float:right;text-align:right;padding-top:0.25em}.dataTables_wrapper .dataTables_paginate .paginate_button{box-sizing:border-box;display:inline-block;min-width:1.5em;padding:0.5em 1em;margin-left:2px;text-align:center;text-decoration:none !important;cursor:pointer;*cursor:hand;color:#333 !important;border:1px solid transparent;border-radius:2px}.dataTables_wrapper .dataTables_paginate .paginate_button.current,.dataTables_wrapper .dataTables_paginate .paginate_button.current:hover{color:#333 !important;border:1px solid #979797;background-color:white;background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #fff), color-stop(100%, #dcdcdc));background:-webkit-linear-gradient(top, #fff 0%, #dcdcdc 100%);background:-moz-linear-gradient(top, #fff 0%, #dcdcdc 100%);background:-ms-linear-gradient(top, #fff 0%, #dcdcdc 100%);background:-o-linear-gradient(top, #fff 0%, #dcdcdc 100%);background:linear-gradient(to bottom, #fff 0%, #dcdcdc 100%)}.dataTables_wrapper .dataTables_paginate .paginate_button.disabled,.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:hover,.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:active{cursor:default;color:#666 !important;border:1px solid transparent;background:transparent;box-shadow:none}.dataTables_wrapper .dataTables_paginate .paginate_button:hover{color:white !important;border:1px solid #111;background-color:#585858;background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #585858), color-stop(100%, #111));background:-webkit-linear-gradient(top, #585858 0%, #111 100%);background:-moz-linear-gradient(top, #585858 0%, #111 100%);background:-ms-linear-gradient(top, #585858 0%, #111 100%);background:-o-linear-gradient(top, #585858 0%, #111 100%);background:linear-gradient(to bottom, #585858 0%, #111 100%)}.dataTables_wrapper .dataTables_paginate .paginate_button:active{outline:none;background-color:#2b2b2b;background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #2b2b2b), color-stop(100%, #0c0c0c));background:-webkit-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-moz-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-ms-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-o-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:linear-gradient(to bottom, #2b2b2b 0%, #0c0c0c 100%);box-shadow:inset 0 0 3px #111}.dataTables_wrapper .dataTables_paginate .ellipsis{padding:0 1em}.dataTables_wrapper .dataTables_processing{position:absolute;top:50%;left:50%;width:100%;height:40px;margin-left:-50%;margin-top:-25px;padding-top:20px;text-align:center;font-size:1.2em;background-color:white;background:-webkit-gradient(linear, left top, right top, color-stop(0%, rgba(255,255,255,0)), color-stop(25%, rgba(255,255,255,0.9)), color-stop(75%, rgba(255,255,255,0.9)), color-stop(100%, rgba(255,255,255,0)));background:-webkit-linear-gradient(left, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%);background:-moz-linear-gradient(left, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%);background:-ms-linear-gradient(left, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%);background:-o-linear-gradient(left, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%);background:linear-gradient(to right, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%)}.dataTables_wrapper .dataTables_length,.dataTables_wrapper .dataTables_filter,.dataTables_wrapper .dataTables_info,.dataTables_wrapper .dataTables_processing,.dataTables_wrapper .dataTables_paginate{color:#333}.dataTables_wrapper .dataTables_scroll{clear:both}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody{*margin-top:-1px;-webkit-overflow-scrolling:touch}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>th,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>td,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>th,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>td{vertical-align:middle}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>th>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>td>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>th>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>td>div.dataTables_sizing{height:0;overflow:hidden;margin:0 !important;padding:0 !important}.dataTables_wrapper.no-footer .dataTables_scrollBody{border-bottom:1px solid #111}.dataTables_wrapper.no-footer div.dataTables_scrollHead table.dataTable,.dataTables_wrapper.no-footer div.dataTables_scrollBody>table{border-bottom:none}.dataTables_wrapper:after{visibility:hidden;display:block;content:"";clear:both;height:0}@media screen and (max-width: 767px){.dataTables_wrapper .dataTables_info,.dataTables_wrapper .dataTables_paginate{float:none;text-align:center}.dataTables_wrapper .dataTables_paginate{margin-top:0.5em}}@media screen and (max-width: 640px){.dataTables_wrapper .dataTables_length,.dataTables_wrapper .dataTables_filter{float:none;text-align:center}.dataTables_wrapper .dataTables_filter{margin-top:0.5em}}table.dataTable thead th div.DataTables_sort_wrapper{position:relative}table.dataTable thead th div.DataTables_sort_wrapper span{position:absolute;top:50%;margin-top:-8px;right:-18px}table.dataTable thead th.ui-state-default,table.dataTable tfoot th.ui-state-default{border-left-width:0}table.dataTable thead th.ui-state-default:first-child,table.dataTable tfoot th.ui-state-default:first-child{border-left-width:1px}.dataTables_wrapper .dataTables_paginate .fg-button{box-sizing:border-box;display:inline-block;min-width:1.5em;padding:0.5em;margin-left:2px;text-align:center;text-decoration:none !important;cursor:pointer;*cursor:hand;border:1px solid transparent}.dataTables_wrapper .dataTables_paginate .fg-button:active{outline:none}.dataTables_wrapper .dataTables_paginate .fg-button:first-child{border-top-left-radius:3px;border-bottom-left-radius:3px}.dataTables_wrapper .dataTables_paginate .fg-button:last-child{border-top-right-radius:3px;border-bottom-right-radius:3px}.dataTables_wrapper .ui-widget-header{font-weight:normal}.dataTables_wrapper .ui-toolbar{padding:8px}.dataTables_wrapper.no-footer .dataTables_scrollBody{border-bottom:none}.dataTables_wrapper .dataTables_length,.dataTables_wrapper .dataTables_filter,.dataTables_wrapper .dataTables_info,.dataTables_wrapper .dataTables_processing,.dataTables_wrapper .dataTables_paginate{color:inherit} diff --git a/wwwroot/assets/extra-libs/DataTables/DataTables-1.10.16/css/dataTables.semanticui.css b/wwwroot/assets/extra-libs/DataTables/DataTables-1.10.16/css/dataTables.semanticui.css new file mode 100644 index 0000000..2583842 --- /dev/null +++ b/wwwroot/assets/extra-libs/DataTables/DataTables-1.10.16/css/dataTables.semanticui.css @@ -0,0 +1,102 @@ +/* + * Styling for DataTables with Semantic UI + */ +table.dataTable.table { + margin: 0; +} +table.dataTable.table thead th, +table.dataTable.table thead td { + position: relative; +} +table.dataTable.table thead th.sorting, table.dataTable.table thead th.sorting_asc, table.dataTable.table thead th.sorting_desc, +table.dataTable.table thead td.sorting, +table.dataTable.table thead td.sorting_asc, +table.dataTable.table thead td.sorting_desc { + padding-right: 20px; +} +table.dataTable.table thead th.sorting:after, table.dataTable.table thead th.sorting_asc:after, table.dataTable.table thead th.sorting_desc:after, +table.dataTable.table thead td.sorting:after, +table.dataTable.table thead td.sorting_asc:after, +table.dataTable.table thead td.sorting_desc:after { + position: absolute; + top: 12px; + right: 8px; + display: block; + font-family: Icons; +} +table.dataTable.table thead th.sorting:after, +table.dataTable.table thead td.sorting:after { + content: "\f0dc"; + color: #ddd; + font-size: 0.8em; +} +table.dataTable.table thead th.sorting_asc:after, +table.dataTable.table thead td.sorting_asc:after { + content: "\f0de"; +} +table.dataTable.table thead th.sorting_desc:after, +table.dataTable.table thead td.sorting_desc:after { + content: "\f0dd"; +} +table.dataTable.table td, +table.dataTable.table th { + -webkit-box-sizing: content-box; + box-sizing: content-box; +} +table.dataTable.table td.dataTables_empty, +table.dataTable.table th.dataTables_empty { + text-align: center; +} +table.dataTable.table.nowrap th, +table.dataTable.table.nowrap td { + white-space: nowrap; +} + +div.dataTables_wrapper div.dataTables_length select { + vertical-align: middle; + min-height: 2.7142em; +} +div.dataTables_wrapper div.dataTables_length .ui.selection.dropdown { + min-width: 0; +} +div.dataTables_wrapper div.dataTables_filter input { + margin-left: 0.5em; +} +div.dataTables_wrapper div.dataTables_info { + padding-top: 13px; + white-space: nowrap; +} +div.dataTables_wrapper div.dataTables_processing { + position: absolute; + top: 50%; + left: 50%; + width: 200px; + margin-left: -100px; + text-align: center; +} +div.dataTables_wrapper div.row.dt-table { + padding: 0; +} +div.dataTables_wrapper div.dataTables_scrollHead table.dataTable { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; + border-bottom: none; +} +div.dataTables_wrapper div.dataTables_scrollBody thead .sorting:after, +div.dataTables_wrapper div.dataTables_scrollBody thead .sorting_asc:after, +div.dataTables_wrapper div.dataTables_scrollBody thead .sorting_desc:after { + display: none; +} +div.dataTables_wrapper div.dataTables_scrollBody table.dataTable { + border-radius: 0; + border-top: none; + border-bottom-width: 0; +} +div.dataTables_wrapper div.dataTables_scrollBody table.dataTable.no-footer { + border-bottom-width: 1px; +} +div.dataTables_wrapper div.dataTables_scrollFoot table.dataTable { + border-top-right-radius: 0; + border-top-left-radius: 0; + border-top: none; +} diff --git a/wwwroot/assets/extra-libs/DataTables/DataTables-1.10.16/css/dataTables.semanticui.min.css b/wwwroot/assets/extra-libs/DataTables/DataTables-1.10.16/css/dataTables.semanticui.min.css new file mode 100644 index 0000000..c192a34 --- /dev/null +++ b/wwwroot/assets/extra-libs/DataTables/DataTables-1.10.16/css/dataTables.semanticui.min.css @@ -0,0 +1 @@ +table.dataTable.table{margin:0}table.dataTable.table thead th,table.dataTable.table thead td{position:relative}table.dataTable.table thead th.sorting,table.dataTable.table thead th.sorting_asc,table.dataTable.table thead th.sorting_desc,table.dataTable.table thead td.sorting,table.dataTable.table thead td.sorting_asc,table.dataTable.table thead td.sorting_desc{padding-right:20px}table.dataTable.table thead th.sorting:after,table.dataTable.table thead th.sorting_asc:after,table.dataTable.table thead th.sorting_desc:after,table.dataTable.table thead td.sorting:after,table.dataTable.table thead td.sorting_asc:after,table.dataTable.table thead td.sorting_desc:after{position:absolute;top:12px;right:8px;display:block;font-family:Icons}table.dataTable.table thead th.sorting:after,table.dataTable.table thead td.sorting:after{content:"\f0dc";color:#ddd;font-size:0.8em}table.dataTable.table thead th.sorting_asc:after,table.dataTable.table thead td.sorting_asc:after{content:"\f0de"}table.dataTable.table thead th.sorting_desc:after,table.dataTable.table thead td.sorting_desc:after{content:"\f0dd"}table.dataTable.table td,table.dataTable.table th{-webkit-box-sizing:content-box;box-sizing:content-box}table.dataTable.table td.dataTables_empty,table.dataTable.table th.dataTables_empty{text-align:center}table.dataTable.table.nowrap th,table.dataTable.table.nowrap td{white-space:nowrap}div.dataTables_wrapper div.dataTables_length select{vertical-align:middle;min-height:2.7142em}div.dataTables_wrapper div.dataTables_length .ui.selection.dropdown{min-width:0}div.dataTables_wrapper div.dataTables_filter input{margin-left:0.5em}div.dataTables_wrapper div.dataTables_info{padding-top:13px;white-space:nowrap}div.dataTables_wrapper div.dataTables_processing{position:absolute;top:50%;left:50%;width:200px;margin-left:-100px;text-align:center}div.dataTables_wrapper div.row.dt-table{padding:0}div.dataTables_wrapper div.dataTables_scrollHead table.dataTable{border-bottom-right-radius:0;border-bottom-left-radius:0;border-bottom:none}div.dataTables_wrapper div.dataTables_scrollBody thead .sorting:after,div.dataTables_wrapper div.dataTables_scrollBody thead .sorting_asc:after,div.dataTables_wrapper div.dataTables_scrollBody thead .sorting_desc:after{display:none}div.dataTables_wrapper div.dataTables_scrollBody table.dataTable{border-radius:0;border-top:none;border-bottom-width:0}div.dataTables_wrapper div.dataTables_scrollBody table.dataTable.no-footer{border-bottom-width:1px}div.dataTables_wrapper div.dataTables_scrollFoot table.dataTable{border-top-right-radius:0;border-top-left-radius:0;border-top:none} diff --git a/wwwroot/assets/extra-libs/DataTables/DataTables-1.10.16/css/jquery.dataTables.css b/wwwroot/assets/extra-libs/DataTables/DataTables-1.10.16/css/jquery.dataTables.css new file mode 100644 index 0000000..760eccb --- /dev/null +++ b/wwwroot/assets/extra-libs/DataTables/DataTables-1.10.16/css/jquery.dataTables.css @@ -0,0 +1,448 @@ +/* + * Table styles + */ +table.dataTable { + width: 100%; + margin: 0 auto; + clear: both; + border-collapse: separate; + border-spacing: 0; + /* + * Header and footer styles + */ + /* + * Body styles + */ +} +table.dataTable thead th, +table.dataTable tfoot th { + font-weight: bold; +} +table.dataTable thead th, +table.dataTable thead td { + padding: 10px 18px; + border-bottom: 1px solid #111; +} +table.dataTable thead th:active, +table.dataTable thead td:active { + outline: none; +} +table.dataTable tfoot th, +table.dataTable tfoot td { + padding: 10px 18px 6px 18px; + border-top: 1px solid #111; +} +table.dataTable thead .sorting, +table.dataTable thead .sorting_asc, +table.dataTable thead .sorting_desc, +table.dataTable thead .sorting_asc_disabled, +table.dataTable thead .sorting_desc_disabled { + cursor: pointer; + *cursor: hand; + background-repeat: no-repeat; + background-position: center right; +} +table.dataTable thead .sorting { + background-image: url("../images/sort_both.png"); +} +table.dataTable thead .sorting_asc { + background-image: url("../images/sort_asc.png"); +} +table.dataTable thead .sorting_desc { + background-image: url("../images/sort_desc.png"); +} +table.dataTable thead .sorting_asc_disabled { + background-image: url("../images/sort_asc_disabled.png"); +} +table.dataTable thead .sorting_desc_disabled { + background-image: url("../images/sort_desc_disabled.png"); +} +table.dataTable tbody tr { + background-color: #ffffff; +} +table.dataTable tbody tr.selected { + background-color: #B0BED9; +} +table.dataTable tbody th, +table.dataTable tbody td { + padding: 8px 10px; +} +table.dataTable.row-border tbody th, table.dataTable.row-border tbody td, table.dataTable.display tbody th, table.dataTable.display tbody td { + border-top: 1px solid #ddd; +} +table.dataTable.row-border tbody tr:first-child th, +table.dataTable.row-border tbody tr:first-child td, table.dataTable.display tbody tr:first-child th, +table.dataTable.display tbody tr:first-child td { + border-top: none; +} +table.dataTable.cell-border tbody th, table.dataTable.cell-border tbody td { + border-top: 1px solid #ddd; + border-right: 1px solid #ddd; +} +table.dataTable.cell-border tbody tr th:first-child, +table.dataTable.cell-border tbody tr td:first-child { + border-left: 1px solid #ddd; +} +table.dataTable.cell-border tbody tr:first-child th, +table.dataTable.cell-border tbody tr:first-child td { + border-top: none; +} +table.dataTable.stripe tbody tr.odd, table.dataTable.display tbody tr.odd { + background-color: #f9f9f9; +} +table.dataTable.stripe tbody tr.odd.selected, table.dataTable.display tbody tr.odd.selected { + background-color: #acbad4; +} +table.dataTable.hover tbody tr:hover, table.dataTable.display tbody tr:hover { + background-color: #f6f6f6; +} +table.dataTable.hover tbody tr:hover.selected, table.dataTable.display tbody tr:hover.selected { + background-color: #aab7d1; +} +table.dataTable.order-column tbody tr > .sorting_1, +table.dataTable.order-column tbody tr > .sorting_2, +table.dataTable.order-column tbody tr > .sorting_3, table.dataTable.display tbody tr > .sorting_1, +table.dataTable.display tbody tr > .sorting_2, +table.dataTable.display tbody tr > .sorting_3 { + background-color: #fafafa; +} +table.dataTable.order-column tbody tr.selected > .sorting_1, +table.dataTable.order-column tbody tr.selected > .sorting_2, +table.dataTable.order-column tbody tr.selected > .sorting_3, table.dataTable.display tbody tr.selected > .sorting_1, +table.dataTable.display tbody tr.selected > .sorting_2, +table.dataTable.display tbody tr.selected > .sorting_3 { + background-color: #acbad5; +} +table.dataTable.display tbody tr.odd > .sorting_1, table.dataTable.order-column.stripe tbody tr.odd > .sorting_1 { + background-color: #f1f1f1; +} +table.dataTable.display tbody tr.odd > .sorting_2, table.dataTable.order-column.stripe tbody tr.odd > .sorting_2 { + background-color: #f3f3f3; +} +table.dataTable.display tbody tr.odd > .sorting_3, table.dataTable.order-column.stripe tbody tr.odd > .sorting_3 { + background-color: whitesmoke; +} +table.dataTable.display tbody tr.odd.selected > .sorting_1, table.dataTable.order-column.stripe tbody tr.odd.selected > .sorting_1 { + background-color: #a6b4cd; +} +table.dataTable.display tbody tr.odd.selected > .sorting_2, table.dataTable.order-column.stripe tbody tr.odd.selected > .sorting_2 { + background-color: #a8b5cf; +} +table.dataTable.display tbody tr.odd.selected > .sorting_3, table.dataTable.order-column.stripe tbody tr.odd.selected > .sorting_3 { + background-color: #a9b7d1; +} +table.dataTable.display tbody tr.even > .sorting_1, table.dataTable.order-column.stripe tbody tr.even > .sorting_1 { + background-color: #fafafa; +} +table.dataTable.display tbody tr.even > .sorting_2, table.dataTable.order-column.stripe tbody tr.even > .sorting_2 { + background-color: #fcfcfc; +} +table.dataTable.display tbody tr.even > .sorting_3, table.dataTable.order-column.stripe tbody tr.even > .sorting_3 { + background-color: #fefefe; +} +table.dataTable.display tbody tr.even.selected > .sorting_1, table.dataTable.order-column.stripe tbody tr.even.selected > .sorting_1 { + background-color: #acbad5; +} +table.dataTable.display tbody tr.even.selected > .sorting_2, table.dataTable.order-column.stripe tbody tr.even.selected > .sorting_2 { + background-color: #aebcd6; +} +table.dataTable.display tbody tr.even.selected > .sorting_3, table.dataTable.order-column.stripe tbody tr.even.selected > .sorting_3 { + background-color: #afbdd8; +} +table.dataTable.display tbody tr:hover > .sorting_1, table.dataTable.order-column.hover tbody tr:hover > .sorting_1 { + background-color: #eaeaea; +} +table.dataTable.display tbody tr:hover > .sorting_2, table.dataTable.order-column.hover tbody tr:hover > .sorting_2 { + background-color: #ececec; +} +table.dataTable.display tbody tr:hover > .sorting_3, table.dataTable.order-column.hover tbody tr:hover > .sorting_3 { + background-color: #efefef; +} +table.dataTable.display tbody tr:hover.selected > .sorting_1, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_1 { + background-color: #a2aec7; +} +table.dataTable.display tbody tr:hover.selected > .sorting_2, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_2 { + background-color: #a3b0c9; +} +table.dataTable.display tbody tr:hover.selected > .sorting_3, table.dataTable.order-column.hover tbody tr:hover.selected > .sorting_3 { + background-color: #a5b2cb; +} +table.dataTable.no-footer { + border-bottom: 1px solid #111; +} +table.dataTable.nowrap th, table.dataTable.nowrap td { + white-space: nowrap; +} +table.dataTable.compact thead th, +table.dataTable.compact thead td { + padding: 4px 17px 4px 4px; +} +table.dataTable.compact tfoot th, +table.dataTable.compact tfoot td { + padding: 4px; +} +table.dataTable.compact tbody th, +table.dataTable.compact tbody td { + padding: 4px; +} +table.dataTable th.dt-left, +table.dataTable td.dt-left { + text-align: left; +} +table.dataTable th.dt-center, +table.dataTable td.dt-center, +table.dataTable td.dataTables_empty { + text-align: center; +} +table.dataTable th.dt-right, +table.dataTable td.dt-right { + text-align: right; +} +table.dataTable th.dt-justify, +table.dataTable td.dt-justify { + text-align: justify; +} +table.dataTable th.dt-nowrap, +table.dataTable td.dt-nowrap { + white-space: nowrap; +} +table.dataTable thead th.dt-head-left, +table.dataTable thead td.dt-head-left, +table.dataTable tfoot th.dt-head-left, +table.dataTable tfoot td.dt-head-left { + text-align: left; +} +table.dataTable thead th.dt-head-center, +table.dataTable thead td.dt-head-center, +table.dataTable tfoot th.dt-head-center, +table.dataTable tfoot td.dt-head-center { + text-align: center; +} +table.dataTable thead th.dt-head-right, +table.dataTable thead td.dt-head-right, +table.dataTable tfoot th.dt-head-right, +table.dataTable tfoot td.dt-head-right { + text-align: right; +} +table.dataTable thead th.dt-head-justify, +table.dataTable thead td.dt-head-justify, +table.dataTable tfoot th.dt-head-justify, +table.dataTable tfoot td.dt-head-justify { + text-align: justify; +} +table.dataTable thead th.dt-head-nowrap, +table.dataTable thead td.dt-head-nowrap, +table.dataTable tfoot th.dt-head-nowrap, +table.dataTable tfoot td.dt-head-nowrap { + white-space: nowrap; +} +table.dataTable tbody th.dt-body-left, +table.dataTable tbody td.dt-body-left { + text-align: left; +} +table.dataTable tbody th.dt-body-center, +table.dataTable tbody td.dt-body-center { + text-align: center; +} +table.dataTable tbody th.dt-body-right, +table.dataTable tbody td.dt-body-right { + text-align: right; +} +table.dataTable tbody th.dt-body-justify, +table.dataTable tbody td.dt-body-justify { + text-align: justify; +} +table.dataTable tbody th.dt-body-nowrap, +table.dataTable tbody td.dt-body-nowrap { + white-space: nowrap; +} + +table.dataTable, +table.dataTable th, +table.dataTable td { + box-sizing: content-box; +} + +/* + * Control feature layout + */ +.dataTables_wrapper { + position: relative; + clear: both; + *zoom: 1; + zoom: 1; +} +.dataTables_wrapper .dataTables_length { + float: left; +} +.dataTables_wrapper .dataTables_filter { + float: right; + text-align: right; +} +.dataTables_wrapper .dataTables_filter input { + margin-left: 0.5em; +} +.dataTables_wrapper .dataTables_info { + clear: both; + float: left; + padding-top: 0.755em; +} +.dataTables_wrapper .dataTables_paginate { + float: right; + text-align: right; + padding-top: 0.25em; +} +.dataTables_wrapper .dataTables_paginate .paginate_button { + box-sizing: border-box; + display: inline-block; + min-width: 1.5em; + padding: 0.5em 1em; + margin-left: 2px; + text-align: center; + text-decoration: none !important; + cursor: pointer; + *cursor: hand; + color: #333 !important; + border: 1px solid transparent; + border-radius: 2px; +} +.dataTables_wrapper .dataTables_paginate .paginate_button.current, .dataTables_wrapper .dataTables_paginate .paginate_button.current:hover { + color: #333 !important; + border: 1px solid #979797; + background-color: white; + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, white), color-stop(100%, #dcdcdc)); + /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(top, white 0%, #dcdcdc 100%); + /* Chrome10+,Safari5.1+ */ + background: -moz-linear-gradient(top, white 0%, #dcdcdc 100%); + /* FF3.6+ */ + background: -ms-linear-gradient(top, white 0%, #dcdcdc 100%); + /* IE10+ */ + background: -o-linear-gradient(top, white 0%, #dcdcdc 100%); + /* Opera 11.10+ */ + background: linear-gradient(to bottom, white 0%, #dcdcdc 100%); + /* W3C */ +} +.dataTables_wrapper .dataTables_paginate .paginate_button.disabled, .dataTables_wrapper .dataTables_paginate .paginate_button.disabled:hover, .dataTables_wrapper .dataTables_paginate .paginate_button.disabled:active { + cursor: default; + color: #666 !important; + border: 1px solid transparent; + background: transparent; + box-shadow: none; +} +.dataTables_wrapper .dataTables_paginate .paginate_button:hover { + color: white !important; + border: 1px solid #111; + background-color: #585858; + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #585858), color-stop(100%, #111)); + /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(top, #585858 0%, #111 100%); + /* Chrome10+,Safari5.1+ */ + background: -moz-linear-gradient(top, #585858 0%, #111 100%); + /* FF3.6+ */ + background: -ms-linear-gradient(top, #585858 0%, #111 100%); + /* IE10+ */ + background: -o-linear-gradient(top, #585858 0%, #111 100%); + /* Opera 11.10+ */ + background: linear-gradient(to bottom, #585858 0%, #111 100%); + /* W3C */ +} +.dataTables_wrapper .dataTables_paginate .paginate_button:active { + outline: none; + background-color: #2b2b2b; + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #2b2b2b), color-stop(100%, #0c0c0c)); + /* Chrome,Safari4+ */ + background: -webkit-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); + /* Chrome10+,Safari5.1+ */ + background: -moz-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); + /* FF3.6+ */ + background: -ms-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); + /* IE10+ */ + background: -o-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%); + /* Opera 11.10+ */ + background: linear-gradient(to bottom, #2b2b2b 0%, #0c0c0c 100%); + /* W3C */ + box-shadow: inset 0 0 3px #111; +} +.dataTables_wrapper .dataTables_paginate .ellipsis { + padding: 0 1em; +} +.dataTables_wrapper .dataTables_processing { + position: absolute; + top: 50%; + left: 50%; + width: 100%; + height: 40px; + margin-left: -50%; + margin-top: -25px; + padding-top: 20px; + text-align: center; + font-size: 1.2em; + background-color: white; + background: -webkit-gradient(linear, left top, right top, color-stop(0%, rgba(255, 255, 255, 0)), color-stop(25%, rgba(255, 255, 255, 0.9)), color-stop(75%, rgba(255, 255, 255, 0.9)), color-stop(100%, rgba(255, 255, 255, 0))); + background: -webkit-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%); + background: -moz-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%); + background: -ms-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%); + background: -o-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%); + background: linear-gradient(to right, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.9) 25%, rgba(255, 255, 255, 0.9) 75%, rgba(255, 255, 255, 0) 100%); +} +.dataTables_wrapper .dataTables_length, +.dataTables_wrapper .dataTables_filter, +.dataTables_wrapper .dataTables_info, +.dataTables_wrapper .dataTables_processing, +.dataTables_wrapper .dataTables_paginate { + color: #333; +} +.dataTables_wrapper .dataTables_scroll { + clear: both; +} +.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody { + *margin-top: -1px; + -webkit-overflow-scrolling: touch; +} +.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > th, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > td, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > th, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > td { + vertical-align: middle; +} +.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > th > div.dataTables_sizing, +.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > thead > tr > td > div.dataTables_sizing, .dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > th > div.dataTables_sizing, +.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody > table > tbody > tr > td > div.dataTables_sizing { + height: 0; + overflow: hidden; + margin: 0 !important; + padding: 0 !important; +} +.dataTables_wrapper.no-footer .dataTables_scrollBody { + border-bottom: 1px solid #111; +} +.dataTables_wrapper.no-footer div.dataTables_scrollHead table.dataTable, +.dataTables_wrapper.no-footer div.dataTables_scrollBody > table { + border-bottom: none; +} +.dataTables_wrapper:after { + visibility: hidden; + display: block; + content: ""; + clear: both; + height: 0; +} + +@media screen and (max-width: 767px) { + .dataTables_wrapper .dataTables_info, + .dataTables_wrapper .dataTables_paginate { + float: none; + text-align: center; + } + .dataTables_wrapper .dataTables_paginate { + margin-top: 0.5em; + } +} +@media screen and (max-width: 640px) { + .dataTables_wrapper .dataTables_length, + .dataTables_wrapper .dataTables_filter { + float: none; + text-align: center; + } + .dataTables_wrapper .dataTables_filter { + margin-top: 0.5em; + } +} diff --git a/wwwroot/assets/extra-libs/DataTables/DataTables-1.10.16/css/jquery.dataTables.min.css b/wwwroot/assets/extra-libs/DataTables/DataTables-1.10.16/css/jquery.dataTables.min.css new file mode 100644 index 0000000..6565b40 --- /dev/null +++ b/wwwroot/assets/extra-libs/DataTables/DataTables-1.10.16/css/jquery.dataTables.min.css @@ -0,0 +1 @@ +table.dataTable{width:100%;margin:0 auto;clear:both;border-collapse:separate;border-spacing:0}table.dataTable thead th,table.dataTable tfoot th{font-weight:bold}table.dataTable thead th,table.dataTable thead td{padding:10px 18px;border-bottom:1px solid #111}table.dataTable thead th:active,table.dataTable thead td:active{outline:none}table.dataTable tfoot th,table.dataTable tfoot td{padding:10px 18px 6px 18px;border-top:1px solid #111}table.dataTable thead .sorting,table.dataTable thead .sorting_asc,table.dataTable thead .sorting_desc,table.dataTable thead .sorting_asc_disabled,table.dataTable thead .sorting_desc_disabled{cursor:pointer;*cursor:hand;background-repeat:no-repeat;background-position:center right}table.dataTable thead .sorting{background-image:url("../images/sort_both.png")}table.dataTable thead .sorting_asc{background-image:url("../images/sort_asc.png")}table.dataTable thead .sorting_desc{background-image:url("../images/sort_desc.png")}table.dataTable thead .sorting_asc_disabled{background-image:url("../images/sort_asc_disabled.png")}table.dataTable thead .sorting_desc_disabled{background-image:url("../images/sort_desc_disabled.png")}table.dataTable tbody tr{background-color:#ffffff}table.dataTable tbody tr.selected{background-color:#B0BED9}table.dataTable tbody th,table.dataTable tbody td{padding:8px 10px}table.dataTable.row-border tbody th,table.dataTable.row-border tbody td,table.dataTable.display tbody th,table.dataTable.display tbody td{border-top:1px solid #ddd}table.dataTable.row-border tbody tr:first-child th,table.dataTable.row-border tbody tr:first-child td,table.dataTable.display tbody tr:first-child th,table.dataTable.display tbody tr:first-child td{border-top:none}table.dataTable.cell-border tbody th,table.dataTable.cell-border tbody td{border-top:1px solid #ddd;border-right:1px solid #ddd}table.dataTable.cell-border tbody tr th:first-child,table.dataTable.cell-border tbody tr td:first-child{border-left:1px solid #ddd}table.dataTable.cell-border tbody tr:first-child th,table.dataTable.cell-border tbody tr:first-child td{border-top:none}table.dataTable.stripe tbody tr.odd,table.dataTable.display tbody tr.odd{background-color:#f9f9f9}table.dataTable.stripe tbody tr.odd.selected,table.dataTable.display tbody tr.odd.selected{background-color:#acbad4}table.dataTable.hover tbody tr:hover,table.dataTable.display tbody tr:hover{background-color:#f6f6f6}table.dataTable.hover tbody tr:hover.selected,table.dataTable.display tbody tr:hover.selected{background-color:#aab7d1}table.dataTable.order-column tbody tr>.sorting_1,table.dataTable.order-column tbody tr>.sorting_2,table.dataTable.order-column tbody tr>.sorting_3,table.dataTable.display tbody tr>.sorting_1,table.dataTable.display tbody tr>.sorting_2,table.dataTable.display tbody tr>.sorting_3{background-color:#fafafa}table.dataTable.order-column tbody tr.selected>.sorting_1,table.dataTable.order-column tbody tr.selected>.sorting_2,table.dataTable.order-column tbody tr.selected>.sorting_3,table.dataTable.display tbody tr.selected>.sorting_1,table.dataTable.display tbody tr.selected>.sorting_2,table.dataTable.display tbody tr.selected>.sorting_3{background-color:#acbad5}table.dataTable.display tbody tr.odd>.sorting_1,table.dataTable.order-column.stripe tbody tr.odd>.sorting_1{background-color:#f1f1f1}table.dataTable.display tbody tr.odd>.sorting_2,table.dataTable.order-column.stripe tbody tr.odd>.sorting_2{background-color:#f3f3f3}table.dataTable.display tbody tr.odd>.sorting_3,table.dataTable.order-column.stripe tbody tr.odd>.sorting_3{background-color:whitesmoke}table.dataTable.display tbody tr.odd.selected>.sorting_1,table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_1{background-color:#a6b4cd}table.dataTable.display tbody tr.odd.selected>.sorting_2,table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_2{background-color:#a8b5cf}table.dataTable.display tbody tr.odd.selected>.sorting_3,table.dataTable.order-column.stripe tbody tr.odd.selected>.sorting_3{background-color:#a9b7d1}table.dataTable.display tbody tr.even>.sorting_1,table.dataTable.order-column.stripe tbody tr.even>.sorting_1{background-color:#fafafa}table.dataTable.display tbody tr.even>.sorting_2,table.dataTable.order-column.stripe tbody tr.even>.sorting_2{background-color:#fcfcfc}table.dataTable.display tbody tr.even>.sorting_3,table.dataTable.order-column.stripe tbody tr.even>.sorting_3{background-color:#fefefe}table.dataTable.display tbody tr.even.selected>.sorting_1,table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_1{background-color:#acbad5}table.dataTable.display tbody tr.even.selected>.sorting_2,table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_2{background-color:#aebcd6}table.dataTable.display tbody tr.even.selected>.sorting_3,table.dataTable.order-column.stripe tbody tr.even.selected>.sorting_3{background-color:#afbdd8}table.dataTable.display tbody tr:hover>.sorting_1,table.dataTable.order-column.hover tbody tr:hover>.sorting_1{background-color:#eaeaea}table.dataTable.display tbody tr:hover>.sorting_2,table.dataTable.order-column.hover tbody tr:hover>.sorting_2{background-color:#ececec}table.dataTable.display tbody tr:hover>.sorting_3,table.dataTable.order-column.hover tbody tr:hover>.sorting_3{background-color:#efefef}table.dataTable.display tbody tr:hover.selected>.sorting_1,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_1{background-color:#a2aec7}table.dataTable.display tbody tr:hover.selected>.sorting_2,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_2{background-color:#a3b0c9}table.dataTable.display tbody tr:hover.selected>.sorting_3,table.dataTable.order-column.hover tbody tr:hover.selected>.sorting_3{background-color:#a5b2cb}table.dataTable.no-footer{border-bottom:1px solid #111}table.dataTable.nowrap th,table.dataTable.nowrap td{white-space:nowrap}table.dataTable.compact thead th,table.dataTable.compact thead td{padding:4px 17px 4px 4px}table.dataTable.compact tfoot th,table.dataTable.compact tfoot td{padding:4px}table.dataTable.compact tbody th,table.dataTable.compact tbody td{padding:4px}table.dataTable th.dt-left,table.dataTable td.dt-left{text-align:left}table.dataTable th.dt-center,table.dataTable td.dt-center,table.dataTable td.dataTables_empty{text-align:center}table.dataTable th.dt-right,table.dataTable td.dt-right{text-align:right}table.dataTable th.dt-justify,table.dataTable td.dt-justify{text-align:justify}table.dataTable th.dt-nowrap,table.dataTable td.dt-nowrap{white-space:nowrap}table.dataTable thead th.dt-head-left,table.dataTable thead td.dt-head-left,table.dataTable tfoot th.dt-head-left,table.dataTable tfoot td.dt-head-left{text-align:left}table.dataTable thead th.dt-head-center,table.dataTable thead td.dt-head-center,table.dataTable tfoot th.dt-head-center,table.dataTable tfoot td.dt-head-center{text-align:center}table.dataTable thead th.dt-head-right,table.dataTable thead td.dt-head-right,table.dataTable tfoot th.dt-head-right,table.dataTable tfoot td.dt-head-right{text-align:right}table.dataTable thead th.dt-head-justify,table.dataTable thead td.dt-head-justify,table.dataTable tfoot th.dt-head-justify,table.dataTable tfoot td.dt-head-justify{text-align:justify}table.dataTable thead th.dt-head-nowrap,table.dataTable thead td.dt-head-nowrap,table.dataTable tfoot th.dt-head-nowrap,table.dataTable tfoot td.dt-head-nowrap{white-space:nowrap}table.dataTable tbody th.dt-body-left,table.dataTable tbody td.dt-body-left{text-align:left}table.dataTable tbody th.dt-body-center,table.dataTable tbody td.dt-body-center{text-align:center}table.dataTable tbody th.dt-body-right,table.dataTable tbody td.dt-body-right{text-align:right}table.dataTable tbody th.dt-body-justify,table.dataTable tbody td.dt-body-justify{text-align:justify}table.dataTable tbody th.dt-body-nowrap,table.dataTable tbody td.dt-body-nowrap{white-space:nowrap}table.dataTable,table.dataTable th,table.dataTable td{box-sizing:content-box}.dataTables_wrapper{position:relative;clear:both;*zoom:1;zoom:1}.dataTables_wrapper .dataTables_length{float:left}.dataTables_wrapper .dataTables_filter{float:right;text-align:right}.dataTables_wrapper .dataTables_filter input{margin-left:0.5em}.dataTables_wrapper .dataTables_info{clear:both;float:left;padding-top:0.755em}.dataTables_wrapper .dataTables_paginate{float:right;text-align:right;padding-top:0.25em}.dataTables_wrapper .dataTables_paginate .paginate_button{box-sizing:border-box;display:inline-block;min-width:1.5em;padding:0.5em 1em;margin-left:2px;text-align:center;text-decoration:none !important;cursor:pointer;*cursor:hand;color:#333 !important;border:1px solid transparent;border-radius:2px}.dataTables_wrapper .dataTables_paginate .paginate_button.current,.dataTables_wrapper .dataTables_paginate .paginate_button.current:hover{color:#333 !important;border:1px solid #979797;background-color:white;background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #fff), color-stop(100%, #dcdcdc));background:-webkit-linear-gradient(top, #fff 0%, #dcdcdc 100%);background:-moz-linear-gradient(top, #fff 0%, #dcdcdc 100%);background:-ms-linear-gradient(top, #fff 0%, #dcdcdc 100%);background:-o-linear-gradient(top, #fff 0%, #dcdcdc 100%);background:linear-gradient(to bottom, #fff 0%, #dcdcdc 100%)}.dataTables_wrapper .dataTables_paginate .paginate_button.disabled,.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:hover,.dataTables_wrapper .dataTables_paginate .paginate_button.disabled:active{cursor:default;color:#666 !important;border:1px solid transparent;background:transparent;box-shadow:none}.dataTables_wrapper .dataTables_paginate .paginate_button:hover{color:white !important;border:1px solid #111;background-color:#585858;background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #585858), color-stop(100%, #111));background:-webkit-linear-gradient(top, #585858 0%, #111 100%);background:-moz-linear-gradient(top, #585858 0%, #111 100%);background:-ms-linear-gradient(top, #585858 0%, #111 100%);background:-o-linear-gradient(top, #585858 0%, #111 100%);background:linear-gradient(to bottom, #585858 0%, #111 100%)}.dataTables_wrapper .dataTables_paginate .paginate_button:active{outline:none;background-color:#2b2b2b;background:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #2b2b2b), color-stop(100%, #0c0c0c));background:-webkit-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-moz-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-ms-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:-o-linear-gradient(top, #2b2b2b 0%, #0c0c0c 100%);background:linear-gradient(to bottom, #2b2b2b 0%, #0c0c0c 100%);box-shadow:inset 0 0 3px #111}.dataTables_wrapper .dataTables_paginate .ellipsis{padding:0 1em}.dataTables_wrapper .dataTables_processing{position:absolute;top:50%;left:50%;width:100%;height:40px;margin-left:-50%;margin-top:-25px;padding-top:20px;text-align:center;font-size:1.2em;background-color:white;background:-webkit-gradient(linear, left top, right top, color-stop(0%, rgba(255,255,255,0)), color-stop(25%, rgba(255,255,255,0.9)), color-stop(75%, rgba(255,255,255,0.9)), color-stop(100%, rgba(255,255,255,0)));background:-webkit-linear-gradient(left, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%);background:-moz-linear-gradient(left, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%);background:-ms-linear-gradient(left, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%);background:-o-linear-gradient(left, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%);background:linear-gradient(to right, rgba(255,255,255,0) 0%, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%)}.dataTables_wrapper .dataTables_length,.dataTables_wrapper .dataTables_filter,.dataTables_wrapper .dataTables_info,.dataTables_wrapper .dataTables_processing,.dataTables_wrapper .dataTables_paginate{color:#333}.dataTables_wrapper .dataTables_scroll{clear:both}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody{*margin-top:-1px;-webkit-overflow-scrolling:touch}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>th,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>td,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>th,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>td{vertical-align:middle}.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>th>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>thead>tr>td>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>th>div.dataTables_sizing,.dataTables_wrapper .dataTables_scroll div.dataTables_scrollBody>table>tbody>tr>td>div.dataTables_sizing{height:0;overflow:hidden;margin:0 !important;padding:0 !important}.dataTables_wrapper.no-footer .dataTables_scrollBody{border-bottom:1px solid #111}.dataTables_wrapper.no-footer div.dataTables_scrollHead table.dataTable,.dataTables_wrapper.no-footer div.dataTables_scrollBody>table{border-bottom:none}.dataTables_wrapper:after{visibility:hidden;display:block;content:"";clear:both;height:0}@media screen and (max-width: 767px){.dataTables_wrapper .dataTables_info,.dataTables_wrapper .dataTables_paginate{float:none;text-align:center}.dataTables_wrapper .dataTables_paginate{margin-top:0.5em}}@media screen and (max-width: 640px){.dataTables_wrapper .dataTables_length,.dataTables_wrapper .dataTables_filter{float:none;text-align:center}.dataTables_wrapper .dataTables_filter{margin-top:0.5em}} diff --git a/wwwroot/assets/extra-libs/DataTables/DataTables-1.10.16/images/sort_asc.png b/wwwroot/assets/extra-libs/DataTables/DataTables-1.10.16/images/sort_asc.png new file mode 100644 index 0000000..e1ba61a Binary files /dev/null and b/wwwroot/assets/extra-libs/DataTables/DataTables-1.10.16/images/sort_asc.png differ diff --git a/wwwroot/assets/extra-libs/DataTables/DataTables-1.10.16/images/sort_asc_disabled.png b/wwwroot/assets/extra-libs/DataTables/DataTables-1.10.16/images/sort_asc_disabled.png new file mode 100644 index 0000000..fb11dfe Binary files /dev/null and b/wwwroot/assets/extra-libs/DataTables/DataTables-1.10.16/images/sort_asc_disabled.png differ diff --git a/wwwroot/assets/extra-libs/DataTables/DataTables-1.10.16/images/sort_both.png b/wwwroot/assets/extra-libs/DataTables/DataTables-1.10.16/images/sort_both.png new file mode 100644 index 0000000..af5bc7c Binary files /dev/null and b/wwwroot/assets/extra-libs/DataTables/DataTables-1.10.16/images/sort_both.png differ diff --git a/wwwroot/assets/extra-libs/DataTables/DataTables-1.10.16/images/sort_desc.png b/wwwroot/assets/extra-libs/DataTables/DataTables-1.10.16/images/sort_desc.png new file mode 100644 index 0000000..0e156de Binary files /dev/null and b/wwwroot/assets/extra-libs/DataTables/DataTables-1.10.16/images/sort_desc.png differ diff --git a/wwwroot/assets/extra-libs/DataTables/DataTables-1.10.16/images/sort_desc_disabled.png b/wwwroot/assets/extra-libs/DataTables/DataTables-1.10.16/images/sort_desc_disabled.png new file mode 100644 index 0000000..c9fdd8a Binary files /dev/null and b/wwwroot/assets/extra-libs/DataTables/DataTables-1.10.16/images/sort_desc_disabled.png differ diff --git a/wwwroot/assets/extra-libs/DataTables/DataTables-1.10.16/js/dataTables.bootstrap.js b/wwwroot/assets/extra-libs/DataTables/DataTables-1.10.16/js/dataTables.bootstrap.js new file mode 100644 index 0000000..774b93e --- /dev/null +++ b/wwwroot/assets/extra-libs/DataTables/DataTables-1.10.16/js/dataTables.bootstrap.js @@ -0,0 +1,182 @@ +/*! DataTables Bootstrap 3 integration + * ©2011-2015 SpryMedia Ltd - datatables.net/license + */ + +/** + * DataTables integration for Bootstrap 3. This requires Bootstrap 3 and + * DataTables 1.10 or newer. + * + * This file sets the defaults and adds options to DataTables to style its + * controls using Bootstrap. See http://datatables.net/manual/styling/bootstrap + * for further information. + */ +(function (factory) { + if (typeof define === "function" && define.amd) { + // AMD + define(["jquery", "datatables.net"], function ($) { + return factory($, window, document); + }); + } else if (typeof exports === "object") { + // CommonJS + module.exports = function (root, $) { + if (!root) { + root = window; + } + + if (!$ || !$.fn.dataTable) { + // Require DataTables, which attaches to jQuery, including + // jQuery if needed and have a $ property so we can access the + // jQuery object that is used + $ = require("datatables.net")(root, $).$; + } + + return factory($, root, root.document); + }; + } else { + // Browser + factory(jQuery, window, document); + } +})(function ($, window, document, undefined) { + "use strict"; + var DataTable = $.fn.dataTable; + + /* Set the defaults for DataTables initialisation */ + $.extend(true, DataTable.defaults, { + dom: + "<'row'<'col-sm-6'l><'col-sm-6'f>>" + + "<'row'<'col-sm-12'tr>>" + + "<'row'<'col-sm-5'i><'col-sm-7'p>>", + renderer: "bootstrap", + }); + + /* Default class modification */ + $.extend(DataTable.ext.classes, { + sWrapper: "dataTables_wrapper form-inline dt-bootstrap", + sFilterInput: "form-control input-sm", + sLengthSelect: "form-control input-sm", + sProcessing: "dataTables_processing panel panel-default", + }); + + /* Bootstrap paging button renderer */ + DataTable.ext.renderer.pageButton.bootstrap = function ( + settings, + host, + idx, + buttons, + page, + pages + ) { + var api = new DataTable.Api(settings); + var classes = settings.oClasses; + var lang = settings.oLanguage.oPaginate; + var aria = settings.oLanguage.oAria.paginate || {}; + var btnDisplay, + btnClass, + counter = 0; + + var attach = function (container, buttons) { + var i, ien, node, button; + var clickHandler = function (e) { + e.preventDefault(); + if ( + !$(e.currentTarget).hasClass("disabled") && + api.page() != e.data.action + ) { + api.page(e.data.action).draw("page"); + } + }; + + for (i = 0, ien = buttons.length; i < ien; i++) { + button = buttons[i]; + + if ($.isArray(button)) { + attach(container, button); + } else { + btnDisplay = ""; + btnClass = ""; + + switch (button) { + case "ellipsis": + btnDisplay = "…"; + btnClass = "disabled"; + break; + + case "first": + btnDisplay = lang.sFirst; + btnClass = button + (page > 0 ? "" : " disabled"); + break; + + case "previous": + btnDisplay = lang.sPrevious; + btnClass = button + (page > 0 ? "" : " disabled"); + break; + + case "next": + btnDisplay = lang.sNext; + btnClass = button + (page < pages - 1 ? "" : " disabled"); + break; + + case "last": + btnDisplay = lang.sLast; + btnClass = button + (page < pages - 1 ? "" : " disabled"); + break; + + default: + btnDisplay = button + 1; + btnClass = page === button ? "active" : ""; + break; + } + + if (btnDisplay) { + node = $("
  • ", { + class: classes.sPageButton + " " + btnClass, + id: + idx === 0 && typeof button === "string" + ? settings.sTableId + "_" + button + : null, + }) + .append( + $("", { + href: "#", + "aria-controls": settings.sTableId, + "aria-label": aria[button], + "data-dt-idx": counter, + tabindex: settings.iTabIndex, + }).html(btnDisplay) + ) + .appendTo(container); + + settings.oApi._fnBindAction(node, { action: button }, clickHandler); + + counter++; + } + } + } + }; + + // IE9 throws an 'unknown error' if document.activeElement is used + // inside an iframe or frame. + var activeEl; + + try { + // Because this approach is destroying and recreating the paging + // elements, focus is lost on the select button which is bad for + // accessibility. So we want to restore focus once the draw has + // completed + activeEl = $(host).find(document.activeElement).data("dt-idx"); + } catch (e) {} + + attach( + $(host).empty().html('