Initial commit
This commit is contained in:
@@ -0,0 +1,304 @@
|
||||
@implements IAsyncDisposable
|
||||
|
||||
@using MudBlazor
|
||||
@using Microsoft.JSInterop
|
||||
@using Microsoft.AspNetCore.Components.Authorization
|
||||
@using RobotNet10.ScriptEditor.Clients
|
||||
@using RobotNet10.ScriptEditor.Dialogs
|
||||
@using RobotNet10.ScriptEngine.Shared
|
||||
@using RobotNet10.Shared
|
||||
@using RobotNet10.Components
|
||||
|
||||
@inject InstanceMissionHubClient InstanceMissionClient
|
||||
@inject IDialogService DialogService
|
||||
@inject ISnackbar Snackbar
|
||||
@inject IJSRuntime JSRuntime
|
||||
@inject AuthenticationStateProvider AuthenticationStateProvider
|
||||
|
||||
<div class="w-100 h-100 p-3">
|
||||
<div @ref="_containerRef" class="w-100 h-100">
|
||||
<MudTable @ref="table" T="InstanceMissionDto"
|
||||
ServerData="@(new Func<TableState, CancellationToken, Task<TableData<InstanceMissionDto>>>(LoadData))"
|
||||
Hover="true"
|
||||
Striped="true"
|
||||
Dense="true"
|
||||
Height="@_tableHeight"
|
||||
Loading="@_isLoading">
|
||||
<ToolBarContent>
|
||||
<div @ref="toolbarRef" class="w-100 d-flex flex-row">
|
||||
<MudText Typo="Typo.h6">Instance Missions</MudText>
|
||||
<MudSpacer />
|
||||
<MudIconButton Class="me-2" Icon="@Icons.Material.Filled.Refresh" Color="Color.Primary" Size="Size.Small" OnClick="OnSearch" />
|
||||
<MudTextField T="string" Immediate="true" OnAdornmentClick="OnSearch" OnKeyDown="@(async (KeyboardEventArgs e) => { if (e.Key == "Enter") await OnSearch(); })"
|
||||
OnDebounceIntervalElapsed="OnSearch" DebounceInterval="1000" Value="@_searchText" Margin="Margin.Dense"
|
||||
Placeholder="Search" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Search"
|
||||
IconSize="Size.Medium" Class="mt-0" Variant="Variant.Outlined" />
|
||||
</div>
|
||||
</ToolBarContent>
|
||||
<HeaderContent>
|
||||
<MudTh>Mission Name</MudTh>
|
||||
<MudTh>State</MudTh>
|
||||
<MudTh>Score</MudTh>
|
||||
<MudTh>Created At</MudTh>
|
||||
<MudTh>Stopped At</MudTh>
|
||||
<MudTh>Actions</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd DataLabel="Mission Name">@context.MissionName</MudTd>
|
||||
<MudTd DataLabel="State">
|
||||
<MudChip T="string" Size="Size.Small" Color="@GetStateColor(context.State)">
|
||||
@context.State
|
||||
</MudChip>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Score">@($"{(100.0 * context.Score / @context.TotalScore):#.00}%")</MudTd>
|
||||
<MudTd DataLabel="Created At">@context.CreatedAt.ToString("yyyy-MM-dd HH:mm:ss")</MudTd>
|
||||
<MudTd DataLabel="Created At">@(GetStoppedAtString(context))</MudTd>
|
||||
<MudTd DataLabel="Actions">
|
||||
<div style="display: flex; gap: 4px;">
|
||||
<IconButton Icon="script-text"
|
||||
Title="View Log"
|
||||
OnClick="@(() => HandleViewLog(context))" />
|
||||
@if (context.State == ScriptMissionState.Running || context.State == ScriptMissionState.Paused || context.State == ScriptMissionState.Pausing)
|
||||
{
|
||||
<IconButton Icon="cancel"
|
||||
Title="Cancel Mission"
|
||||
OnClick="@(() => HandleCancelMission(context))" />
|
||||
}
|
||||
@if (context.State == ScriptMissionState.Running)
|
||||
{
|
||||
<IconButton Icon="pause"
|
||||
Title="Pause Mission"
|
||||
OnClick="@(() => HandlePauseMission(context))" />
|
||||
}
|
||||
@if (context.State == ScriptMissionState.Paused)
|
||||
{
|
||||
<IconButton Icon="play"
|
||||
Title="Resume Mission"
|
||||
OnClick="@(() => HandleResumeMission(context))" />
|
||||
}
|
||||
</div>
|
||||
</MudTd>
|
||||
</RowTemplate>
|
||||
<NoRecordsContent>
|
||||
<MudText>No matching records found</MudText>
|
||||
</NoRecordsContent>
|
||||
<LoadingContent>
|
||||
<MudText>Loading...</MudText>
|
||||
</LoadingContent>
|
||||
<PagerContent>
|
||||
<MudTablePager />
|
||||
</PagerContent>
|
||||
</MudTable>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private ElementReference _containerRef;
|
||||
private ElementReference toolbarRef;
|
||||
private MudTable<InstanceMissionDto> table = default!;
|
||||
private string _tableHeight = "400px";
|
||||
private string _searchText = "";
|
||||
private bool _isLoading = false;
|
||||
private int _totalItems = 0;
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
await base.OnAfterRenderAsync(firstRender);
|
||||
if (firstRender)
|
||||
{
|
||||
await InstanceMissionClient.StartAsync();
|
||||
await CalculateTableHeight();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CalculateTableHeight()
|
||||
{
|
||||
var rect = await _containerRef.MudGetBoundingClientRectAsync();
|
||||
var toolbarRect = await toolbarRef.MudGetBoundingClientRectAsync();
|
||||
_tableHeight = $"{rect.Height - 70 - Math.Max(toolbarRect.Height, 64)}px";
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task<TableData<InstanceMissionDto>> LoadData(TableState state, CancellationToken cancellationToken)
|
||||
{
|
||||
_isLoading = true;
|
||||
StateHasChanged();
|
||||
|
||||
try
|
||||
{
|
||||
var request = new SearchRequest(
|
||||
Page: state.Page + 1, // MudTable uses 0-based page, but our API uses 1-based
|
||||
Size: state.PageSize,
|
||||
TxtSearch: _searchText
|
||||
);
|
||||
|
||||
var result = await InstanceMissionClient.SearchInstanceMissionsAsync(request);
|
||||
_totalItems = result.Total;
|
||||
|
||||
return new TableData<InstanceMissionDto>
|
||||
{
|
||||
Items = result.Items,
|
||||
TotalItems = result.Total
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"Error loading missions: {ex.Message}", Severity.Error);
|
||||
return new TableData<InstanceMissionDto>
|
||||
{
|
||||
Items = [],
|
||||
TotalItems = 0
|
||||
};
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isLoading = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OnSearch()
|
||||
{
|
||||
await table.ReloadServerData();
|
||||
}
|
||||
|
||||
private Color GetStateColor(ScriptMissionState state)
|
||||
{
|
||||
return state switch
|
||||
{
|
||||
ScriptMissionState.Running => Color.Success,
|
||||
ScriptMissionState.Paused => Color.Warning,
|
||||
ScriptMissionState.Pausing => Color.Warning,
|
||||
ScriptMissionState.Resuming => Color.Info,
|
||||
ScriptMissionState.Completed => Color.Success,
|
||||
ScriptMissionState.Canceled => Color.Default,
|
||||
ScriptMissionState.Error => Color.Error,
|
||||
_ => Color.Default
|
||||
};
|
||||
}
|
||||
|
||||
private async Task HandleViewLog(InstanceMissionDto mission)
|
||||
{
|
||||
var parameters = new DialogParameters<MissionLogDialog>
|
||||
{
|
||||
{ x => x.MissionId, mission.Id },
|
||||
{ x => x.MissionName, mission.MissionName },
|
||||
{ x => x.State, mission.State },
|
||||
{ x => x.InitialLog, mission.Log }
|
||||
};
|
||||
|
||||
var options = new DialogOptions
|
||||
{
|
||||
MaxWidth = MaxWidth.Large,
|
||||
FullWidth = true,
|
||||
CloseButton = true
|
||||
};
|
||||
|
||||
await DialogService.ShowAsync<MissionLogDialog>($"Mission Log: {mission.MissionName}", parameters, options);
|
||||
}
|
||||
|
||||
private async Task HandleCancelMission(InstanceMissionDto mission)
|
||||
{
|
||||
var parameters = new DialogParameters<CancelMissionDialog>
|
||||
{
|
||||
{ x => x.MissionName, mission.MissionName }
|
||||
};
|
||||
|
||||
var options = new DialogOptions
|
||||
{
|
||||
MaxWidth = MaxWidth.Small,
|
||||
FullWidth = true
|
||||
};
|
||||
|
||||
var dialog = await DialogService.ShowAsync<CancelMissionDialog>("Cancel Mission", parameters, options);
|
||||
var result = await dialog.Result;
|
||||
|
||||
if (result is not null && !result.Canceled && result.Data is string userReason)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Get current user information
|
||||
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
|
||||
var userName = authState?.User?.Identity?.Name ?? "Unknown";
|
||||
|
||||
// Combine user reason with user information
|
||||
var reason = string.IsNullOrWhiteSpace(userReason)
|
||||
? $"Canceled by {userName}"
|
||||
: $"Canceled by {userName}: {userReason.Trim()}";
|
||||
|
||||
var messageResult = await InstanceMissionClient.CancelMissionAsync(mission.Id, reason);
|
||||
if (messageResult.IsSuccess)
|
||||
{
|
||||
Snackbar.Add(messageResult.Message ?? "Mission canceled", Severity.Success);
|
||||
await table.ReloadServerData();
|
||||
}
|
||||
else
|
||||
{
|
||||
Snackbar.Add(messageResult.Message ?? "Failed to cancel mission", Severity.Error);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"Error canceling mission: {ex.Message}", Severity.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandlePauseMission(InstanceMissionDto mission)
|
||||
{
|
||||
try
|
||||
{
|
||||
var messageResult = await InstanceMissionClient.PauseMissionAsync(mission.Id);
|
||||
if (messageResult.IsSuccess)
|
||||
{
|
||||
Snackbar.Add(messageResult.Message ?? "Mission paused", Severity.Success);
|
||||
}
|
||||
else
|
||||
{
|
||||
Snackbar.Add(messageResult.Message ?? "Failed to pause mission", Severity.Error);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"Error pausing mission: {ex.Message}", Severity.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleResumeMission(InstanceMissionDto mission)
|
||||
{
|
||||
try
|
||||
{
|
||||
var messageResult = await InstanceMissionClient.ResumeMissionAsync(mission.Id);
|
||||
if (messageResult.IsSuccess)
|
||||
{
|
||||
Snackbar.Add(messageResult.Message ?? "Mission resumed", Severity.Success);
|
||||
}
|
||||
else
|
||||
{
|
||||
Snackbar.Add(messageResult.Message ?? "Failed to resume mission", Severity.Error);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"Error resuming mission: {ex.Message}", Severity.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private string GetStoppedAtString(InstanceMissionDto mission)
|
||||
{
|
||||
if (mission.State == ScriptMissionState.Canceled || mission.State == ScriptMissionState.Completed || mission.State == ScriptMissionState.Error)
|
||||
{
|
||||
return mission.StoppedAt.ToString("yyyy-MM-dd HH:mm:ss");
|
||||
}
|
||||
else
|
||||
{
|
||||
return "--";
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
await InstanceMissionClient.StopAsync();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user