304 lines
12 KiB
Plaintext
304 lines
12 KiB
Plaintext
@page "/missions"
|
|
@attribute [Authorize]
|
|
|
|
@using RobotNet.Script.Shares
|
|
@using RobotNet.Shares
|
|
@using RobotNet.Clients
|
|
@using RobotNet.WebApp.Scripts.Models
|
|
|
|
@inject IHttpClientFactory httpFactory
|
|
@inject IDialogService DialogService
|
|
@inject ISnackbar Snackbar
|
|
|
|
<PageTitle>Runner Missions</PageTitle>
|
|
|
|
<div @ref="divRef" class="w-100 h-100">
|
|
<MudTable @ref="table" ServerData="ServerReload" Style="margin: 10px;" Items="runnerMissions" Height="@TableHeight" Dense FixedHeader Virtualize Hover Elevation="6">
|
|
<ToolBarContent>
|
|
<div @ref="toolbarRef" class="w-100 d-flex flex-row">
|
|
<h3>Danh sách Missions</h3>
|
|
<MudSpacer />
|
|
<MudIconButton Class="me-2" Icon="@Icons.Material.Filled.Refresh" Color="Color.Primary" Size="Size.Small" OnClick="ReloadTable" />
|
|
<MudTextField T="string" ValueChanged="@(s => OnSearch(s))" DebounceInterval="1000" Value="@searchString" Margin="Margin.Dense"
|
|
Placeholder="Search" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Search"
|
|
IconSize="Size.Medium" Class="mt-0" Variant="Variant.Outlined" />
|
|
</div>
|
|
</ToolBarContent>
|
|
<ColGroup>
|
|
<col style="width:60px;" />
|
|
<col style="width:100px;" />
|
|
<col style="width:150px;" />
|
|
<col style="width:120px;" />
|
|
<col />
|
|
<col style="width:100px;" />
|
|
<col />
|
|
<col style="width:100px;" />
|
|
</ColGroup>
|
|
<HeaderContent>
|
|
<MudTh>STT</MudTh>
|
|
<MudTh>Tên</MudTh>
|
|
<MudTh>Id</MudTh>
|
|
<MudTh>Create at</MudTh>
|
|
<MudTh>Parameters</MudTh>
|
|
<MudTh>Status</MudTh>
|
|
<MudTh>Log</MudTh>
|
|
<MudTh></MudTh>
|
|
</HeaderContent>
|
|
<RowTemplate>
|
|
<MudTd>@context.Index</MudTd>
|
|
<MudTd DataLabel="Tên">@context.MissionName</MudTd>
|
|
<MudTd DataLabel="Id">@context.Id.ToString()[..13]</MudTd>
|
|
<MudTd DataLabel="Create at">@context.CreatedAt.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss")</MudTd>
|
|
<MudTd DataLabel="Parameters">
|
|
@if (context.Parameters?.Any() == true)
|
|
{
|
|
<span title="@context.Parameters">
|
|
@ShortenLog(context.Parameters, 30)
|
|
</span>
|
|
<MudIconButton Icon="@Icons.Material.Filled.Info" Color="Color.Primary" Size="Size.Small"
|
|
OnClick="@(() => ShowParametersDialog(context))" />
|
|
}
|
|
else
|
|
{
|
|
<span>{}</span>
|
|
}
|
|
</MudTd>
|
|
<MudTd DataLabel="Status">
|
|
@if (context.Status == MissionStatus.Running)
|
|
{
|
|
<MudChip T="string" Color="Color.Success" Variant="Variant.Filled">@context.Status</MudChip>
|
|
}
|
|
else if (context.Status == MissionStatus.Completed)
|
|
{
|
|
<MudChip T="string" Color="Color.Primary" Variant="Variant.Filled">@context.Status</MudChip>
|
|
}
|
|
else if (context.Status == MissionStatus.Error)
|
|
{
|
|
<MudChip T="string" Color="Color.Error" Variant="Variant.Filled">@context.Status</MudChip>
|
|
}
|
|
else
|
|
{
|
|
<MudChip T="string" Color="Color.Warning" Variant="Variant.Filled">@context.Status</MudChip>
|
|
}
|
|
</MudTd>
|
|
<MudTd DataLabel="Log">
|
|
@if (!string.IsNullOrEmpty(context.Log))
|
|
{
|
|
<span title="@context.Log">
|
|
@ShortenLog(context.Log, 30)
|
|
</span>
|
|
<MudIconButton Icon="@Icons.Material.Filled.Visibility" Color="Color.Primary" Size="Size.Small"
|
|
OnClick="@(() => ShowLogDialog(context.Log))" />
|
|
}
|
|
</MudTd>
|
|
<MudTd DataLabel="" Class="text-center">
|
|
@if (context.Status == MissionStatus.Running)
|
|
{
|
|
<MudIconButton Icon="@Icons.Material.Filled.Stop" Color="Color.Error" Size="Size.Small" OnClick="@(() => PauseMission(context))" />
|
|
}
|
|
else if (context.Status == MissionStatus.Paused)
|
|
{
|
|
<MudIconButton Icon="@Icons.Material.Filled.PlayArrow" Color="Color.Primary" Size="Size.Small" OnClick="@(() => ResumeMission(context))" />
|
|
}
|
|
|
|
@if (context.Status == MissionStatus.Running || context.Status == MissionStatus.Paused)
|
|
{
|
|
<MudIconButton Icon="@Icons.Material.Filled.Delete" Color="Color.Error" Size="Size.Small" OnClick="@(() => CancelMission(context))" />
|
|
}
|
|
</MudTd>
|
|
</RowTemplate>
|
|
<NoRecordsContent>
|
|
<MudText>No matching records found</MudText>
|
|
</NoRecordsContent>
|
|
<LoadingContent>
|
|
<MudText>Loading...</MudText>
|
|
</LoadingContent>
|
|
<PagerContent>
|
|
<MudTablePager />
|
|
</PagerContent>
|
|
</MudTable>
|
|
</div>
|
|
|
|
<MudDialog @bind-Visible="_logDialogOpen" Style="min-width: 900px;">
|
|
<DialogContent>
|
|
<MudText Typo="Typo.h6">Mission Log</MudText>
|
|
<MudPaper Class="pa-2" Style="overflow-x:auto;">
|
|
<pre style="white-space:pre-wrap; word-break:break-all;">@_selectedLog</pre>
|
|
</MudPaper>
|
|
</DialogContent>
|
|
<DialogActions>
|
|
<MudButton OnClick="() => _logDialogOpen = false" Color="Color.Primary">Close</MudButton>
|
|
</DialogActions>
|
|
</MudDialog>
|
|
|
|
<MudDialog @bind-Visible="_parametersDialogOpen" Style="min-width: 600px;">
|
|
<DialogContent>
|
|
<MudText Typo="Typo.h6">Parameters of mission "@_missionName"</MudText>
|
|
<MudPaper Class="pa-2" Style="overflow-x:auto;">
|
|
<table class="mud-table mud-table-dense" style="width:100%;">
|
|
<thead>
|
|
<tr>
|
|
<th style="text-align:left;">Key</th>
|
|
<th style="text-align:left;">Value</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@foreach (var kv in _selectedParameters)
|
|
{
|
|
<tr>
|
|
<td style="vertical-align:top;">@kv.Key</td>
|
|
<td style="vertical-align:top;">@kv.Value</td>
|
|
</tr>
|
|
}
|
|
</tbody>
|
|
</table>
|
|
</MudPaper>
|
|
</DialogContent>
|
|
<DialogActions>
|
|
<MudButton OnClick="() => _parametersDialogOpen = false" Color="Color.Primary">Close</MudButton>
|
|
</DialogActions>
|
|
</MudDialog>
|
|
|
|
@code {
|
|
private string TableHeight = "0px";
|
|
private ElementReference divRef;
|
|
private ElementReference toolbarRef;
|
|
private MudTable<InstanceMissionModel> table = default!;
|
|
private List<InstanceMissionModel> runnerMissions = [];
|
|
private string searchString = "";
|
|
|
|
private string _missionName = "";
|
|
private bool _parametersDialogOpen = false;
|
|
private IDictionary<string, string> _selectedParameters = new Dictionary<string, string>();
|
|
|
|
private bool _logDialogOpen = false;
|
|
private string _selectedLog = string.Empty;
|
|
|
|
private HttpClient http = default!;
|
|
|
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
|
{
|
|
await base.OnAfterRenderAsync(firstRender);
|
|
if (firstRender)
|
|
{
|
|
var rect = await divRef.MudGetBoundingClientRectAsync();
|
|
var toolbarRect = await toolbarRef.MudGetBoundingClientRectAsync();
|
|
TableHeight = $"{rect.Height - 70 - Math.Max(toolbarRect.Height, 64)}px";
|
|
StateHasChanged();
|
|
|
|
await table.ReloadServerData();
|
|
}
|
|
}
|
|
|
|
private async Task<TableData<InstanceMissionModel>> ServerReload(TableState state, CancellationToken token)
|
|
{
|
|
if (http == null)
|
|
{
|
|
http = httpFactory.CreateClient("ScriptManagerAPI");
|
|
}
|
|
|
|
var url = $"api/ScriptMissions/Runner?txtSearch={Uri.EscapeDataString(searchString)}&page={state.Page + 1}&size={state.PageSize}";
|
|
var response = await http.GetFromJsonAsync<SearchResult<InstanceMissionDto>>(url) ?? new();
|
|
|
|
return new TableData<InstanceMissionModel>()
|
|
{
|
|
TotalItems = response.Total,
|
|
Items = [.. response.Items.Select((item, index) => new InstanceMissionModel(index + 1 + state.Page * state.PageSize, item))]
|
|
};
|
|
}
|
|
|
|
private void ShowParametersDialog(InstanceMissionModel mission)
|
|
{
|
|
try
|
|
{
|
|
_selectedParameters = System.Text.Json.JsonSerializer.Deserialize<IDictionary<string, string>>(mission.Parameters)
|
|
?? throw new Exception($"Can not convert parameters data from \"{mission.Parameters}\"");
|
|
_missionName = mission.MissionName;
|
|
_parametersDialogOpen = true;
|
|
StateHasChanged();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Snackbar.Add($"Failed to parse parameters: {ex.Message}", Severity.Error);
|
|
}
|
|
}
|
|
|
|
private string ShortenLog(string? log, int maxLength)
|
|
{
|
|
if (string.IsNullOrEmpty(log)) return string.Empty;
|
|
return log.Length > maxLength ? log.Substring(0, maxLength) + "..." : log;
|
|
}
|
|
|
|
private void ShowLogDialog(string log)
|
|
{
|
|
_selectedLog = log;
|
|
_logDialogOpen = true;
|
|
StateHasChanged();
|
|
}
|
|
|
|
private async Task OnSearch(string text)
|
|
{
|
|
searchString = text;
|
|
await table.ReloadServerData();
|
|
}
|
|
|
|
private async Task CancelMission(InstanceMissionModel model)
|
|
{
|
|
var response = await http.DeleteFromJsonAsync<MessageResult>($"api/ScriptMissions/Runner/{model.Id}");
|
|
if (response == null)
|
|
{
|
|
Snackbar.Add("Failed to cancel mission: server response error", Severity.Error);
|
|
}
|
|
else if (response.IsSuccess)
|
|
{
|
|
Snackbar.Add("Mission canceled successfully", Severity.Success);
|
|
await table.ReloadServerData();
|
|
}
|
|
else
|
|
{
|
|
Snackbar.Add($"Failed to cancel mission: {response.Message}", Severity.Error);
|
|
}
|
|
}
|
|
|
|
private async Task PauseMission(InstanceMissionModel model)
|
|
{
|
|
var response = await http.PutFromJsonAsync<MessageResult>($"api/ScriptMissions/Runner/{model.Id}/pause", new object());
|
|
if (response == null)
|
|
{
|
|
Snackbar.Add("Failed to pause mission: server response error", Severity.Error);
|
|
}
|
|
else if (response.IsSuccess)
|
|
{
|
|
Snackbar.Add("Mission paused successfully", Severity.Success);
|
|
await table.ReloadServerData();
|
|
}
|
|
else
|
|
{
|
|
Snackbar.Add($"Failed to pause mission: {response.Message}", Severity.Error);
|
|
}
|
|
}
|
|
|
|
private async Task ResumeMission(InstanceMissionModel model)
|
|
{
|
|
var response = await http.PutFromJsonAsync<MessageResult>($"api/ScriptMissions/Runner/{model.Id}/resume", new object());
|
|
if (response == null)
|
|
{
|
|
Snackbar.Add("Failed to resume mission: server response error", Severity.Error);
|
|
}
|
|
else if (response.IsSuccess)
|
|
{
|
|
Snackbar.Add("Mission resumed successfully", Severity.Success);
|
|
await table.ReloadServerData();
|
|
}
|
|
else
|
|
{
|
|
Snackbar.Add($"Failed to resume mission: {response.Message}", Severity.Error);
|
|
}
|
|
}
|
|
|
|
private async Task ReloadTable()
|
|
{
|
|
await table.ReloadServerData();
|
|
}
|
|
}
|