mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-03-07 16:30:28 -05:00
Add log truncate buttons to admin (#180)
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
@inject GlobalLoadingService GlobalLoadingService
|
||||
|
||||
<FullScreenLoadingIndicator @ref="LoadingIndicator" />
|
||||
<ConfirmModal />
|
||||
<TopMenu />
|
||||
<div class="flex pt-16 overflow-hidden bg-gray-50 dark:bg-gray-900">
|
||||
<div id="main-content" class="relative w-full max-w-screen-2xl mx-auto h-full overflow-y-auto bg-gray-50 dark:bg-gray-900">
|
||||
@@ -15,6 +16,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="blazor-error-ui">
|
||||
An unhandled error has occurred.
|
||||
<a href="" class="reload">Reload</a>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<Breadcrumb BreadcrumbItems="BreadcrumbItems" />
|
||||
<div class="flex items-center justify-between">
|
||||
<h1 class="text-xl font-semibold text-gray-900 sm:text-2xl dark:text-white">Emails</h1>
|
||||
<RefreshButton OnRefresh="RefreshData" ButtonText="Refresh" />
|
||||
<RefreshButton OnClick="RefreshData" ButtonText="Refresh" />
|
||||
</div>
|
||||
<p>This page gives an overview of recently received mails by this AliasVault server.</p>
|
||||
</div>
|
||||
|
||||
@@ -9,7 +9,10 @@
|
||||
<Breadcrumb BreadcrumbItems="BreadcrumbItems" />
|
||||
<div class="flex items-center justify-between">
|
||||
<h1 class="text-xl font-semibold text-gray-900 sm:text-2xl dark:text-white">Auth logs</h1>
|
||||
<RefreshButton OnRefresh="RefreshData" ButtonText="Refresh" />
|
||||
<div class="flex items-end space-x-2">
|
||||
<DeleteButton OnClick="DeleteLogsWithConfirmation" ButtonText="Delete all logs" />
|
||||
<RefreshButton OnClick="RefreshData" ButtonText="Refresh" />
|
||||
</div>
|
||||
</div>
|
||||
<p>This page gives an overview of recent auth attempts.</p>
|
||||
</div>
|
||||
@@ -146,4 +149,25 @@ else
|
||||
IsLoading = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task DeleteLogsWithConfirmation()
|
||||
{
|
||||
if (await ConfirmModalService.ShowConfirmation("Confirm Delete", "Are you sure you want to delete all logs? This action cannot be undone."))
|
||||
{
|
||||
await DeleteLogs();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DeleteLogs()
|
||||
{
|
||||
IsLoading = true;
|
||||
StateHasChanged();
|
||||
|
||||
DbContext.AuthLogs.RemoveRange(DbContext.AuthLogs);
|
||||
await DbContext.SaveChangesAsync();
|
||||
await RefreshData();
|
||||
|
||||
IsLoading = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,10 @@
|
||||
<Breadcrumb BreadcrumbItems="BreadcrumbItems" />
|
||||
<div class="flex items-center justify-between">
|
||||
<h1 class="text-xl font-semibold text-gray-900 sm:text-2xl dark:text-white">General logs</h1>
|
||||
<RefreshButton OnRefresh="RefreshData" ButtonText="Refresh" />
|
||||
<div class="flex items-end space-x-2">
|
||||
<DeleteButton OnClick="DeleteLogsWithConfirmation" ButtonText="Delete all logs" />
|
||||
<RefreshButton OnClick="RefreshData" ButtonText="Refresh" />
|
||||
</div>
|
||||
</div>
|
||||
<p>This page gives an overview of recent system logs.</p>
|
||||
</div>
|
||||
@@ -165,4 +168,25 @@ else
|
||||
IsLoading = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task DeleteLogsWithConfirmation()
|
||||
{
|
||||
if (await ConfirmModalService.ShowConfirmation("Confirm Delete", "Are you sure you want to delete all logs? This action cannot be undone."))
|
||||
{
|
||||
await DeleteLogs();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DeleteLogs()
|
||||
{
|
||||
IsLoading = true;
|
||||
StateHasChanged();
|
||||
|
||||
DbContext.Logs.RemoveRange(DbContext.Logs);
|
||||
await DbContext.SaveChangesAsync();
|
||||
await RefreshData();
|
||||
|
||||
IsLoading = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ using AliasServerDb;
|
||||
using AliasVault.Admin.Main.Models;
|
||||
using AliasVault.Admin.Services;
|
||||
using AliasVault.AuthLogging;
|
||||
using AliasVault.RazorComponents.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
@@ -72,6 +73,12 @@ public class MainBase : OwningComponentBase
|
||||
[Inject]
|
||||
protected AuthLoggingService AuthLoggingService { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the confirm modal service.
|
||||
/// </summary>
|
||||
[Inject]
|
||||
protected ConfirmModalService ConfirmModalService { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the injected JSRuntime instance.
|
||||
/// </summary>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<Breadcrumb BreadcrumbItems="BreadcrumbItems" />
|
||||
<div class="flex items-center justify-between">
|
||||
<h1 class="text-xl font-semibold text-gray-900 sm:text-2xl dark:text-white">Users</h1>
|
||||
<RefreshButton OnRefresh="RefreshData" ButtonText="Refresh" />
|
||||
<RefreshButton OnClick="RefreshData" ButtonText="Refresh" />
|
||||
</div>
|
||||
<p>This page gives an overview of all registered users and the associated vaults.</p>
|
||||
</div>
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
@using AliasVault.Admin.Main.Components.WorkerStatus
|
||||
@using AliasVault.RazorComponents
|
||||
@using AliasVault.RazorComponents.Alerts
|
||||
@using AliasVault.RazorComponents.Buttons
|
||||
@using AliasVault.Admin.Main.Models
|
||||
@using AliasVault.Admin.Main.Pages
|
||||
@using AliasVault.Admin.Services
|
||||
|
||||
@@ -15,6 +15,7 @@ using AliasVault.Admin.Main;
|
||||
using AliasVault.Admin.Services;
|
||||
using AliasVault.AuthLogging;
|
||||
using AliasVault.Logging;
|
||||
using AliasVault.RazorComponents.Services;
|
||||
using Cryptography.Server;
|
||||
using Microsoft.AspNetCore.Components.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
@@ -48,6 +49,7 @@ builder.Services.AddScoped<NavigationService>();
|
||||
builder.Services.AddScoped<AuthenticationStateProvider, RevalidatingAuthenticationStateProvider>();
|
||||
builder.Services.AddHttpContextAccessor();
|
||||
builder.Services.AddScoped<AuthLoggingService>();
|
||||
builder.Services.AddScoped<ConfirmModalService>();
|
||||
builder.Services.AddSingleton(new VersionedContentService(Directory.GetCurrentDirectory() + "/wwwroot"));
|
||||
|
||||
builder.Services.AddAuthentication(options =>
|
||||
|
||||
@@ -600,6 +600,10 @@ video {
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
.visible {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.invisible {
|
||||
visibility: hidden;
|
||||
}
|
||||
@@ -620,6 +624,10 @@ video {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.inset-0 {
|
||||
inset: 0px;
|
||||
}
|
||||
|
||||
.right-0 {
|
||||
right: 0px;
|
||||
}
|
||||
@@ -739,6 +747,14 @@ video {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.mt-2 {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.mt-3 {
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
.line-clamp-1 {
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
@@ -858,6 +874,10 @@ video {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.w-96 {
|
||||
width: 24rem;
|
||||
}
|
||||
|
||||
.max-w-2xl {
|
||||
max-width: 42rem;
|
||||
}
|
||||
@@ -892,6 +912,16 @@ video {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
50% {
|
||||
opacity: .5;
|
||||
}
|
||||
}
|
||||
|
||||
.animate-pulse {
|
||||
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
||||
}
|
||||
|
||||
.cursor-not-allowed {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
@@ -936,6 +966,10 @@ video {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.items-end {
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.items-center {
|
||||
align-items: center;
|
||||
}
|
||||
@@ -1004,6 +1038,12 @@ video {
|
||||
margin-bottom: calc(1.5rem * var(--tw-space-y-reverse));
|
||||
}
|
||||
|
||||
.space-x-3 > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-space-x-reverse: 0;
|
||||
margin-right: calc(0.75rem * var(--tw-space-x-reverse));
|
||||
margin-left: calc(0.75rem * calc(1 - var(--tw-space-x-reverse)));
|
||||
}
|
||||
|
||||
.divide-y > :not([hidden]) ~ :not([hidden]) {
|
||||
--tw-divide-y-reverse: 0;
|
||||
border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse)));
|
||||
@@ -1260,6 +1300,21 @@ video {
|
||||
background-color: rgb(234 179 8 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-gray-300 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(209 213 219 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-gray-700 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(55 65 81 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-primary-500 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(244 149 65 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-opacity-50 {
|
||||
--tw-bg-opacity: 0.5;
|
||||
}
|
||||
@@ -1284,6 +1339,10 @@ video {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.p-5 {
|
||||
padding: 1.25rem;
|
||||
}
|
||||
|
||||
.px-2 {
|
||||
padding-left: 0.5rem;
|
||||
padding-right: 0.5rem;
|
||||
@@ -1349,6 +1408,11 @@ video {
|
||||
padding-bottom: 2rem;
|
||||
}
|
||||
|
||||
.px-7 {
|
||||
padding-left: 1.75rem;
|
||||
padding-right: 1.75rem;
|
||||
}
|
||||
|
||||
.pl-2 {
|
||||
padding-left: 0.5rem;
|
||||
}
|
||||
@@ -1640,6 +1704,25 @@ video {
|
||||
background-color: rgb(153 27 27 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.hover\:bg-blue-700:hover {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(29 78 216 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.hover\:bg-gray-400:hover {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(156 163 175 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.hover\:bg-gray-800:hover {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(31 41 55 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.hover\:bg-opacity-80:hover {
|
||||
--tw-bg-opacity: 0.8;
|
||||
}
|
||||
|
||||
.hover\:text-gray-900:hover {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(17 24 39 / var(--tw-text-opacity));
|
||||
@@ -1731,6 +1814,16 @@ video {
|
||||
--tw-ring-color: rgb(252 165 165 / var(--tw-ring-opacity));
|
||||
}
|
||||
|
||||
.focus\:ring-green-300:focus {
|
||||
--tw-ring-opacity: 1;
|
||||
--tw-ring-color: rgb(134 239 172 / var(--tw-ring-opacity));
|
||||
}
|
||||
|
||||
.focus\:ring-yellow-300:focus {
|
||||
--tw-ring-opacity: 1;
|
||||
--tw-ring-color: rgb(253 224 71 / var(--tw-ring-opacity));
|
||||
}
|
||||
|
||||
.focus\:ring-offset-2:focus {
|
||||
--tw-ring-offset-width: 2px;
|
||||
}
|
||||
@@ -2013,6 +2106,26 @@ video {
|
||||
--tw-ring-color: rgb(127 29 29 / var(--tw-ring-opacity));
|
||||
}
|
||||
|
||||
.dark\:focus\:ring-blue-800:focus:is(.dark *) {
|
||||
--tw-ring-opacity: 1;
|
||||
--tw-ring-color: rgb(30 64 175 / var(--tw-ring-opacity));
|
||||
}
|
||||
|
||||
.dark\:focus\:ring-green-800:focus:is(.dark *) {
|
||||
--tw-ring-opacity: 1;
|
||||
--tw-ring-color: rgb(22 101 52 / var(--tw-ring-opacity));
|
||||
}
|
||||
|
||||
.dark\:focus\:ring-yellow-800:focus:is(.dark *) {
|
||||
--tw-ring-opacity: 1;
|
||||
--tw-ring-color: rgb(133 77 14 / var(--tw-ring-opacity));
|
||||
}
|
||||
|
||||
.dark\:focus\:ring-gray-800:focus:is(.dark *) {
|
||||
--tw-ring-opacity: 1;
|
||||
--tw-ring-color: rgb(31 41 55 / var(--tw-ring-opacity));
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.sm\:flex {
|
||||
display: flex;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<CascadingAuthenticationState>
|
||||
<AuthorizeView>
|
||||
<Authorized>
|
||||
<ConfirmModal />
|
||||
<FullScreenLoadingIndicator @ref="LoadingIndicator" />
|
||||
<TopMenu />
|
||||
<div class="flex pt-16 overflow-hidden bg-gray-50 dark:bg-gray-900">
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<Breadcrumb BreadcrumbItems="BreadcrumbItems" />
|
||||
<div class="flex items-center justify-between">
|
||||
<h1 class="text-xl font-semibold text-gray-900 sm:text-2xl dark:text-white">Credentials</h1>
|
||||
<RefreshButton OnRefresh="LoadCredentialsAsync" ButtonText="Refresh" />
|
||||
<RefreshButton OnClick="LoadCredentialsAsync" ButtonText="Refresh" />
|
||||
</div>
|
||||
<p>Find all of your credentials below.</p>
|
||||
</div>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<Breadcrumb BreadcrumbItems="BreadcrumbItems"/>
|
||||
<div class="flex items-center justify-between">
|
||||
<h1 class="text-xl font-semibold text-gray-900 sm:text-2xl dark:text-white">Emails</h1>
|
||||
<RefreshButton OnRefresh="RefreshData" ButtonText="Refresh" />
|
||||
<RefreshButton OnClick="RefreshData" ButtonText="Refresh" />
|
||||
</div>
|
||||
<p>Below you can find all recent emails sent to one of the email addresses used in your credentials.</p>
|
||||
</div>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<Breadcrumb BreadcrumbItems="BreadcrumbItems"/>
|
||||
<div class="flex items-center justify-between">
|
||||
<h1 class="text-xl font-semibold text-gray-900 sm:text-2xl dark:text-white">Security settings</h1>
|
||||
<RefreshButton OnRefresh="LoadData" ButtonText="Refresh" />
|
||||
<RefreshButton OnClick="LoadData" ButtonText="Refresh" />
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-gray-600 dark:text-gray-400">Configure security settings.</p>
|
||||
</div>
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
using AliasVault.Client;
|
||||
using AliasVault.Client.Providers;
|
||||
using AliasVault.RazorComponents.Services;
|
||||
using Blazored.LocalStorage;
|
||||
using Microsoft.AspNetCore.Components.Authorization;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
@@ -72,6 +73,7 @@ builder.Services.AddScoped<KeyboardShortcutService>();
|
||||
builder.Services.AddScoped<JsInteropService>();
|
||||
builder.Services.AddScoped<EmailService>();
|
||||
builder.Services.AddSingleton<ClipboardCopyService>();
|
||||
builder.Services.AddScoped<ConfirmModalService>();
|
||||
|
||||
builder.Services.AddAuthorizationCore();
|
||||
builder.Services.AddBlazoredLocalStorage();
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
@using AliasVault.Client.Services.Database
|
||||
@using AliasVault.RazorComponents
|
||||
@using AliasVault.RazorComponents.Alerts
|
||||
@using AliasVault.RazorComponents.Buttons
|
||||
@using Microsoft.AspNetCore.Components.Authorization
|
||||
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
|
||||
@using Blazored.LocalStorage
|
||||
|
||||
@@ -600,6 +600,10 @@ video {
|
||||
border-width: 0;
|
||||
}
|
||||
|
||||
.visible {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.static {
|
||||
position: static;
|
||||
}
|
||||
@@ -1394,6 +1398,21 @@ video {
|
||||
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-gray-300 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(209 213 219 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-yellow-500 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(234 179 8 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-red-700 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(185 28 28 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-opacity-50 {
|
||||
--tw-bg-opacity: 0.5;
|
||||
}
|
||||
@@ -1921,6 +1940,15 @@ video {
|
||||
background-color: rgb(153 27 27 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.hover\:bg-gray-400:hover {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(156 163 175 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.hover\:bg-opacity-80:hover {
|
||||
--tw-bg-opacity: 0.8;
|
||||
}
|
||||
|
||||
.hover\:from-primary-600:hover {
|
||||
--tw-gradient-from: #d68338 var(--tw-gradient-from-position);
|
||||
--tw-gradient-to: rgb(214 131 56 / 0) var(--tw-gradient-to-position);
|
||||
@@ -2062,6 +2090,11 @@ video {
|
||||
--tw-ring-color: rgb(239 68 68 / var(--tw-ring-opacity));
|
||||
}
|
||||
|
||||
.focus\:ring-yellow-300:focus {
|
||||
--tw-ring-opacity: 1;
|
||||
--tw-ring-color: rgb(253 224 71 / var(--tw-ring-opacity));
|
||||
}
|
||||
|
||||
.focus\:ring-offset-2:focus {
|
||||
--tw-ring-offset-width: 2px;
|
||||
}
|
||||
@@ -2147,6 +2180,11 @@ video {
|
||||
background-color: rgb(30 41 59 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.dark\:bg-red-600:is(.dark *) {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(220 38 38 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.dark\:bg-opacity-80:is(.dark *) {
|
||||
--tw-bg-opacity: 0.8;
|
||||
}
|
||||
@@ -2280,6 +2318,11 @@ video {
|
||||
background-color: rgb(220 38 38 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.dark\:hover\:bg-red-700:hover:is(.dark *) {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(185 28 28 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.dark\:hover\:from-primary-500:hover:is(.dark *) {
|
||||
--tw-gradient-from: #f49541 var(--tw-gradient-from-position);
|
||||
--tw-gradient-to: rgb(244 149 65 / 0) var(--tw-gradient-to-position);
|
||||
@@ -2375,6 +2418,11 @@ video {
|
||||
--tw-ring-color: rgb(127 29 29 / var(--tw-ring-opacity));
|
||||
}
|
||||
|
||||
.dark\:focus\:ring-yellow-800:focus:is(.dark *) {
|
||||
--tw-ring-opacity: 1;
|
||||
--tw-ring-color: rgb(133 77 14 / var(--tw-ring-opacity));
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.sm\:col-span-3 {
|
||||
grid-column: span 3 / span 3;
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
<button @onclick="HandleClick"
|
||||
disabled="@IsDisabled"
|
||||
class="@GetButtonClasses()">
|
||||
@ChildContent
|
||||
</button>
|
||||
|
||||
@code {
|
||||
/// <summary>
|
||||
/// The content to be displayed inside the button.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public RenderFragment? ChildContent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The event to call when the button is clicked.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public EventCallback OnClick { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Specifies whether the button is disabled.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public bool IsDisabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The color theme of the button.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string Color { get; set; } = "primary";
|
||||
|
||||
/// <summary>
|
||||
/// Additional CSS classes to apply to the button.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string AdditionalClasses { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Handles the button click event.
|
||||
/// </summary>
|
||||
private async Task HandleClick()
|
||||
{
|
||||
if (!IsDisabled)
|
||||
{
|
||||
await OnClick.InvokeAsync();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the CSS classes for the button based on its state and color.
|
||||
/// </summary>
|
||||
/// <returns>A string containing the CSS classes for the button.</returns>
|
||||
private string GetButtonClasses()
|
||||
{
|
||||
var baseClasses = "flex center items-center px-3 py-2 text-sm font-medium text-white rounded-lg focus:outline-none focus:ring-4";
|
||||
var colorClasses = Color switch
|
||||
{
|
||||
"primary" => "bg-primary-700 hover:bg-primary-800 focus:ring-primary-300 dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800",
|
||||
"danger" => "bg-red-700 hover:bg-red-800 focus:ring-red-300 dark:bg-red-600 dark:hover:bg-red-700 dark:focus:ring-red-800",
|
||||
"secondary" => "bg-secondary-700 hover:bg-secondary-800 focus:ring-secondary-300 dark:bg-secondary-600 dark:hover:bg-secondary-700 dark:focus:ring-secondary-800",
|
||||
_ => "bg-gray-700 hover:bg-gray-800 focus:ring-gray-300 dark:bg-gray-600 dark:hover:bg-gray-700 dark:focus:ring-gray-800"
|
||||
};
|
||||
var disabledClasses = IsDisabled ? "bg-gray-400 cursor-not-allowed" : "";
|
||||
|
||||
return $"{baseClasses} {colorClasses} {disabledClasses} {AdditionalClasses}".Trim();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<Button OnClick="HandleClick"
|
||||
Color="danger">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
|
||||
</svg>
|
||||
<span class="ml-2">@ButtonText</span>
|
||||
</Button>
|
||||
|
||||
@code {
|
||||
/// <summary>
|
||||
/// The event to call in the parent when the delete button is clicked.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public EventCallback OnClick { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The text to display on the button.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string ButtonText { get; set; } = "Delete";
|
||||
|
||||
/// <summary>
|
||||
/// Handles the button click event.
|
||||
/// </summary>
|
||||
private async Task HandleClick()
|
||||
{
|
||||
await OnClick.InvokeAsync();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
@using System.Timers
|
||||
|
||||
<Button OnClick="HandleClick"
|
||||
IsDisabled="@IsRefreshing"
|
||||
Color="@Color"
|
||||
AdditionalClasses="@AdditionalClasses">
|
||||
<svg class="@GetIconClasses()" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
|
||||
</svg>
|
||||
<span class="ml-2">@ButtonText</span>
|
||||
</Button>
|
||||
|
||||
@code {
|
||||
/// <summary>
|
||||
/// The event to call in the parent when the button is clicked.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public EventCallback OnClick { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The text to display on the button.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string ButtonText { get; set; } = "Refresh";
|
||||
|
||||
/// <summary>
|
||||
/// The color theme of the button.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string Color { get; set; } = "primary";
|
||||
|
||||
/// <summary>
|
||||
/// Additional CSS classes to apply to the button.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string AdditionalClasses { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the button is currently in a refreshing state.
|
||||
/// </summary>
|
||||
private bool IsRefreshing;
|
||||
|
||||
/// <summary>
|
||||
/// Timer used to control the refreshing state duration.
|
||||
/// </summary>
|
||||
private Timer Timer = new();
|
||||
|
||||
/// <summary>
|
||||
/// Handles the button click event.
|
||||
/// </summary>
|
||||
private async Task HandleClick()
|
||||
{
|
||||
if (IsRefreshing) return;
|
||||
|
||||
IsRefreshing = true;
|
||||
await OnClick.InvokeAsync();
|
||||
|
||||
Timer = new Timer(500);
|
||||
Timer.Elapsed += (sender, args) =>
|
||||
{
|
||||
IsRefreshing = false;
|
||||
Timer.Dispose();
|
||||
InvokeAsync(StateHasChanged);
|
||||
};
|
||||
Timer.Start();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the CSS classes for the refresh icon based on the refreshing state.
|
||||
/// </summary>
|
||||
/// <returns>A string containing the CSS classes for the icon.</returns>
|
||||
private string GetIconClasses()
|
||||
{
|
||||
return $"w-4 h-4 {(IsRefreshing ? "animate-spin" : "")}";
|
||||
}
|
||||
}
|
||||
39
src/Utilities/AliasVault.RazorComponents/ConfirmModal.razor
Normal file
39
src/Utilities/AliasVault.RazorComponents/ConfirmModal.razor
Normal file
@@ -0,0 +1,39 @@
|
||||
@inject ConfirmModalService ModalService
|
||||
@using AliasVault.RazorComponents.Services
|
||||
@implements IDisposable
|
||||
|
||||
@if (ModalService.IsVisible)
|
||||
{
|
||||
<div class="fixed inset-0 z-50 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full flex items-center justify-center">
|
||||
<div class="relative p-5 border w-96 shadow-lg rounded-md bg-white">
|
||||
<div class="mt-3 text-center">
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900">@ModalService.Title</h3>
|
||||
<div class="mt-2 px-7 py-3">
|
||||
<p class="text-sm text-gray-500">
|
||||
@ModalService.Message
|
||||
</p>
|
||||
</div>
|
||||
<div class="items-center px-4 py-3">
|
||||
<button id="confirmButton" class="px-4 py-2 bg-primary-500 text-white text-base font-medium rounded-md w-full shadow-sm hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-primary-300" @onclick="() => ModalService.CloseModal(true)">
|
||||
Confirm
|
||||
</button>
|
||||
<button id="cancelButton" class="mt-3 px-4 py-2 bg-gray-300 text-gray-800 text-base font-medium rounded-md w-full shadow-sm hover:bg-gray-400 focus:outline-none focus:ring-2 focus:ring-gray-300" @onclick="() => ModalService.CloseModal(false)">
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@code {
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
ModalService.OnChange += StateHasChanged;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
ModalService.OnChange -= StateHasChanged;
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
@using System.Timers
|
||||
|
||||
<button @onclick="HandleClick"
|
||||
disabled="@IsRefreshing"
|
||||
class="@GetButtonClasses()">
|
||||
<svg class="@GetIconClasses()" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"></path>
|
||||
</svg>
|
||||
<span class="ml-2">@ButtonText</span>
|
||||
</button>
|
||||
|
||||
@code {
|
||||
/// <summary>
|
||||
/// The event to call in the parent when the button is clicked.
|
||||
/// </summary>
|
||||
[Parameter] public EventCallback OnRefresh { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The text to display on the button.
|
||||
/// </summary>
|
||||
[Parameter] public string ButtonText { get; set; } = "Refresh";
|
||||
|
||||
private bool IsRefreshing;
|
||||
private Timer Timer = new();
|
||||
|
||||
private async Task HandleClick()
|
||||
{
|
||||
if (IsRefreshing) return;
|
||||
|
||||
IsRefreshing = true;
|
||||
await OnRefresh.InvokeAsync();
|
||||
|
||||
Timer = new Timer(500);
|
||||
Timer.Elapsed += (sender, args) =>
|
||||
{
|
||||
IsRefreshing = false;
|
||||
Timer.Dispose();
|
||||
InvokeAsync(StateHasChanged);
|
||||
};
|
||||
Timer.Start();
|
||||
}
|
||||
|
||||
private string GetButtonClasses()
|
||||
{
|
||||
return $"flex items-center px-3 py-2 text-sm font-medium text-white rounded-lg focus:outline-none focus:ring-4 focus:ring-primary-300 dark:focus:ring-primary-800 {(IsRefreshing ? "bg-gray-400 cursor-not-allowed" : "bg-primary-700 hover:bg-primary-800 dark:bg-primary-600 dark:hover:bg-primary-700")}";
|
||||
}
|
||||
|
||||
private string GetIconClasses()
|
||||
{
|
||||
return $"w-4 h-4 {(IsRefreshing ? "animate-spin" : "")}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// <copyright file="ConfirmModalService.cs" company="lanedirt">
|
||||
// Copyright (c) lanedirt. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE.md file in the project root for full license information.
|
||||
// </copyright>
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
namespace AliasVault.RazorComponents.Services;
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
/// <summary>
|
||||
/// Service for managing confirmation modals.
|
||||
/// </summary>
|
||||
public class ConfirmModalService
|
||||
{
|
||||
/// <summary>
|
||||
/// Event triggered when the modal state changes.
|
||||
/// </summary>
|
||||
public event Action OnChange = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the title of the modal.
|
||||
/// </summary>
|
||||
public string Title { get; private set; } = "Are you sure?";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the message of the modal.
|
||||
/// </summary>
|
||||
public string Message { get; private set; } = "Are you sure you want to do this?";
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the modal is visible.
|
||||
/// </summary>
|
||||
public bool IsVisible { get; private set; }
|
||||
|
||||
private TaskCompletionSource<bool> _modalTaskCompletionSource = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Shows the confirmation modal and waits for user response.
|
||||
/// </summary>
|
||||
/// <param name="title">The title of the modal.</param>
|
||||
/// <param name="message">The message to display in the modal.</param>
|
||||
/// <returns>A task that completes when the user responds, returning true if confirmed, false if cancelled.</returns>
|
||||
public Task<bool> ShowConfirmation(string title, string message)
|
||||
{
|
||||
Title = title;
|
||||
Message = message;
|
||||
IsVisible = true;
|
||||
_modalTaskCompletionSource = new TaskCompletionSource<bool>();
|
||||
NotifyStateChanged();
|
||||
return _modalTaskCompletionSource.Task;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes the modal with the specified result.
|
||||
/// </summary>
|
||||
/// <param name="result">The result of the confirmation.</param>
|
||||
public void CloseModal(bool result)
|
||||
{
|
||||
IsVisible = false;
|
||||
_modalTaskCompletionSource.TrySetResult(result);
|
||||
NotifyStateChanged();
|
||||
}
|
||||
|
||||
private void NotifyStateChanged() => OnChange?.Invoke();
|
||||
}
|
||||
Reference in New Issue
Block a user