Dashboard: confirm before Close session / Kill worker
Add a shared ConfirmDialog component and route Sessions, Workers, and SessionDetails Close/Kill buttons through it. The dialog shows the target session id and a color-matched confirm button (yellow Close, red Kill); Cancel dismisses without invoking the admin service. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+64
-13
@@ -34,12 +34,12 @@ else
|
||||
<div class="btn-group btn-group-sm" role="group" aria-label="Session admin actions">
|
||||
<button type="button" class="btn btn-outline-warning"
|
||||
disabled="@IsBusy"
|
||||
@onclick="CloseSessionAsync">
|
||||
@onclick="RequestClose">
|
||||
Close session
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-danger"
|
||||
disabled="@IsBusy"
|
||||
@onclick="KillWorkerAsync">
|
||||
@onclick="RequestKill">
|
||||
Kill worker
|
||||
</button>
|
||||
</div>
|
||||
@@ -54,6 +54,18 @@ else
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (CanManage)
|
||||
{
|
||||
<ConfirmDialog IsOpen="@(PendingAction is not null)"
|
||||
Title="@(PendingAction?.Title ?? string.Empty)"
|
||||
Message="@(PendingAction?.Message ?? string.Empty)"
|
||||
ConfirmLabel="@(PendingAction?.ConfirmLabel ?? "Confirm")"
|
||||
ConfirmButtonClass="@(PendingAction?.ConfirmButtonClass ?? "btn-primary")"
|
||||
IsBusy="IsBusy"
|
||||
OnConfirm="ConfirmPendingAsync"
|
||||
OnCancel="CancelPending" />
|
||||
}
|
||||
|
||||
<section class="dashboard-section">
|
||||
<div class="section-heading">
|
||||
<h2>Session</h2>
|
||||
@@ -176,24 +188,55 @@ else
|
||||
}
|
||||
}
|
||||
|
||||
private Task CloseSessionAsync()
|
||||
{
|
||||
return RunAdminActionAsync(user => SessionAdminService.CloseSessionAsync(user, SessionId, CancellationToken.None));
|
||||
}
|
||||
private PendingConfirm? PendingAction { get; set; }
|
||||
|
||||
private Task KillWorkerAsync()
|
||||
{
|
||||
return RunAdminActionAsync(user => SessionAdminService.KillWorkerAsync(user, SessionId, CancellationToken.None));
|
||||
}
|
||||
|
||||
private async Task RunAdminActionAsync(
|
||||
Func<System.Security.Claims.ClaimsPrincipal, Task<DashboardSessionAdminResult>> action)
|
||||
private void RequestClose()
|
||||
{
|
||||
if (IsBusy)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PendingAction = new PendingConfirm(
|
||||
Title: "Close session?",
|
||||
Message: $"Gracefully close session {SessionId}? The worker will be shut down.",
|
||||
ConfirmLabel: "Close",
|
||||
ConfirmButtonClass: "btn-warning",
|
||||
Action: user => SessionAdminService.CloseSessionAsync(user, SessionId, CancellationToken.None));
|
||||
}
|
||||
|
||||
private void RequestKill()
|
||||
{
|
||||
if (IsBusy)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PendingAction = new PendingConfirm(
|
||||
Title: "Kill worker?",
|
||||
Message: $"Forcefully kill the worker for session {SessionId}? This skips graceful shutdown.",
|
||||
ConfirmLabel: "Kill",
|
||||
ConfirmButtonClass: "btn-danger",
|
||||
Action: user => SessionAdminService.KillWorkerAsync(user, SessionId, CancellationToken.None));
|
||||
}
|
||||
|
||||
private void CancelPending()
|
||||
{
|
||||
if (!IsBusy)
|
||||
{
|
||||
PendingAction = null;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ConfirmPendingAsync()
|
||||
{
|
||||
if (IsBusy || PendingAction is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Func<System.Security.Claims.ClaimsPrincipal, Task<DashboardSessionAdminResult>> action = PendingAction.Action;
|
||||
|
||||
IsBusy = true;
|
||||
try
|
||||
{
|
||||
@@ -207,9 +250,17 @@ else
|
||||
finally
|
||||
{
|
||||
IsBusy = false;
|
||||
PendingAction = null;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed record PendingConfirm(
|
||||
string Title,
|
||||
string Message,
|
||||
string ConfirmLabel,
|
||||
string ConfirmButtonClass,
|
||||
Func<System.Security.Claims.ClaimsPrincipal, Task<DashboardSessionAdminResult>> Action);
|
||||
|
||||
private async Task AttachEventsHubAsync()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(SessionId))
|
||||
|
||||
@@ -25,6 +25,18 @@ else
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (CanManage)
|
||||
{
|
||||
<ConfirmDialog IsOpen="@(PendingAction is not null)"
|
||||
Title="@(PendingAction?.Title ?? string.Empty)"
|
||||
Message="@(PendingAction?.Message ?? string.Empty)"
|
||||
ConfirmLabel="@(PendingAction?.ConfirmLabel ?? "Confirm")"
|
||||
ConfirmButtonClass="@(PendingAction?.ConfirmButtonClass ?? "btn-primary")"
|
||||
IsBusy="IsBusy"
|
||||
OnConfirm="ConfirmPendingAsync"
|
||||
OnCancel="CancelPending" />
|
||||
}
|
||||
|
||||
<section class="dashboard-section">
|
||||
@if (Snapshot.Sessions.Count == 0)
|
||||
{
|
||||
@@ -78,12 +90,12 @@ else
|
||||
<div class="btn-group btn-group-sm" role="group" aria-label="Session actions">
|
||||
<button type="button" class="btn btn-outline-warning"
|
||||
disabled="@IsBusy"
|
||||
@onclick="() => CloseSessionAsync(session.SessionId)">
|
||||
@onclick="() => RequestClose(session.SessionId)">
|
||||
Close
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-danger"
|
||||
disabled="@IsBusy"
|
||||
@onclick="() => KillWorkerAsync(session.SessionId)">
|
||||
@onclick="() => RequestKill(session.SessionId)">
|
||||
Kill
|
||||
</button>
|
||||
</div>
|
||||
@@ -116,24 +128,55 @@ else
|
||||
CanManage = SessionAdminService.CanManage(authenticationState.User);
|
||||
}
|
||||
|
||||
private Task CloseSessionAsync(string sessionId)
|
||||
{
|
||||
return RunActionAsync(user => SessionAdminService.CloseSessionAsync(user, sessionId, CancellationToken.None));
|
||||
}
|
||||
private PendingConfirm? PendingAction { get; set; }
|
||||
|
||||
private Task KillWorkerAsync(string sessionId)
|
||||
{
|
||||
return RunActionAsync(user => SessionAdminService.KillWorkerAsync(user, sessionId, CancellationToken.None));
|
||||
}
|
||||
|
||||
private async Task RunActionAsync(
|
||||
Func<System.Security.Claims.ClaimsPrincipal, Task<DashboardSessionAdminResult>> action)
|
||||
private void RequestClose(string sessionId)
|
||||
{
|
||||
if (IsBusy)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PendingAction = new PendingConfirm(
|
||||
Title: "Close session?",
|
||||
Message: $"Gracefully close session {sessionId}? The worker will be shut down.",
|
||||
ConfirmLabel: "Close",
|
||||
ConfirmButtonClass: "btn-warning",
|
||||
Action: user => SessionAdminService.CloseSessionAsync(user, sessionId, CancellationToken.None));
|
||||
}
|
||||
|
||||
private void RequestKill(string sessionId)
|
||||
{
|
||||
if (IsBusy)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PendingAction = new PendingConfirm(
|
||||
Title: "Kill worker?",
|
||||
Message: $"Forcefully kill the worker for session {sessionId}? This skips graceful shutdown.",
|
||||
ConfirmLabel: "Kill",
|
||||
ConfirmButtonClass: "btn-danger",
|
||||
Action: user => SessionAdminService.KillWorkerAsync(user, sessionId, CancellationToken.None));
|
||||
}
|
||||
|
||||
private void CancelPending()
|
||||
{
|
||||
if (!IsBusy)
|
||||
{
|
||||
PendingAction = null;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ConfirmPendingAsync()
|
||||
{
|
||||
if (IsBusy || PendingAction is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Func<System.Security.Claims.ClaimsPrincipal, Task<DashboardSessionAdminResult>> action = PendingAction.Action;
|
||||
|
||||
IsBusy = true;
|
||||
try
|
||||
{
|
||||
@@ -147,6 +190,14 @@ else
|
||||
finally
|
||||
{
|
||||
IsBusy = false;
|
||||
PendingAction = null;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed record PendingConfirm(
|
||||
string Title,
|
||||
string Message,
|
||||
string ConfirmLabel,
|
||||
string ConfirmButtonClass,
|
||||
Func<System.Security.Claims.ClaimsPrincipal, Task<DashboardSessionAdminResult>> Action);
|
||||
}
|
||||
|
||||
@@ -25,6 +25,18 @@ else
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (CanManage)
|
||||
{
|
||||
<ConfirmDialog IsOpen="@(PendingSessionId is not null)"
|
||||
Title="Kill worker?"
|
||||
Message="@($"Forcefully kill the worker for session {PendingSessionId}? This skips graceful shutdown.")"
|
||||
ConfirmLabel="Kill"
|
||||
ConfirmButtonClass="btn-danger"
|
||||
IsBusy="IsBusy"
|
||||
OnConfirm="ConfirmKillAsync"
|
||||
OnCancel="CancelPending" />
|
||||
}
|
||||
|
||||
<section class="dashboard-section">
|
||||
@if (Snapshot.Workers.Count == 0)
|
||||
{
|
||||
@@ -61,7 +73,7 @@ else
|
||||
<td>
|
||||
<button type="button" class="btn btn-sm btn-outline-danger"
|
||||
disabled="@IsBusy"
|
||||
@onclick="() => KillWorkerAsync(worker.SessionId)">
|
||||
@onclick="() => RequestKill(worker.SessionId)">
|
||||
Kill
|
||||
</button>
|
||||
</td>
|
||||
@@ -93,13 +105,34 @@ else
|
||||
CanManage = SessionAdminService.CanManage(authenticationState.User);
|
||||
}
|
||||
|
||||
private async Task KillWorkerAsync(string sessionId)
|
||||
private string? PendingSessionId { get; set; }
|
||||
|
||||
private void RequestKill(string sessionId)
|
||||
{
|
||||
if (IsBusy)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
PendingSessionId = sessionId;
|
||||
}
|
||||
|
||||
private void CancelPending()
|
||||
{
|
||||
if (!IsBusy)
|
||||
{
|
||||
PendingSessionId = null;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ConfirmKillAsync()
|
||||
{
|
||||
if (IsBusy || PendingSessionId is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
string sessionId = PendingSessionId;
|
||||
IsBusy = true;
|
||||
try
|
||||
{
|
||||
@@ -115,6 +148,7 @@ else
|
||||
finally
|
||||
{
|
||||
IsBusy = false;
|
||||
PendingSessionId = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
@if (IsOpen)
|
||||
{
|
||||
<div class="modal-backdrop fade show"></div>
|
||||
<div class="modal fade show" role="dialog" aria-modal="true" aria-labelledby="@TitleId" tabindex="-1" style="display: block;">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h2 class="modal-title h5" id="@TitleId">@Title</h2>
|
||||
<button type="button" class="btn-close" aria-label="Close"
|
||||
disabled="@IsBusy"
|
||||
@onclick="OnCancel"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p class="mb-0">@Message</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline-secondary"
|
||||
disabled="@IsBusy"
|
||||
@onclick="OnCancel">
|
||||
Cancel
|
||||
</button>
|
||||
<button type="button" class="btn @ConfirmButtonClass"
|
||||
disabled="@IsBusy"
|
||||
@onclick="OnConfirm">
|
||||
@ConfirmLabel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@code {
|
||||
private readonly string TitleId = $"confirm-dialog-{Guid.NewGuid():N}";
|
||||
|
||||
[Parameter]
|
||||
public bool IsOpen { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string Title { get; set; } = "Confirm";
|
||||
|
||||
[Parameter]
|
||||
public string Message { get; set; } = string.Empty;
|
||||
|
||||
[Parameter]
|
||||
public string ConfirmLabel { get; set; } = "Confirm";
|
||||
|
||||
[Parameter]
|
||||
public string ConfirmButtonClass { get; set; } = "btn-primary";
|
||||
|
||||
[Parameter]
|
||||
public bool IsBusy { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public EventCallback OnConfirm { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public EventCallback OnCancel { get; set; }
|
||||
}
|
||||
Reference in New Issue
Block a user