Initial commit

This commit is contained in:
2026-07-03 16:37:12 +07:00
commit 63b8c1ea8b
1931 changed files with 640587 additions and 0 deletions

View File

@@ -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);
}
}