432 lines
18 KiB
Plaintext
432 lines
18 KiB
Plaintext
@page "/traffic-manager"
|
|
@attribute [Authorize]
|
|
|
|
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
|
|
@using RobotNet.MapShares.Dtos
|
|
@using RobotNet.RobotShares.Dtos
|
|
@using RobotNet.RobotShares.Models
|
|
@using RobotNet.Shares
|
|
@using RobotNet.WebApp.Clients
|
|
@using RobotNet.WebApp.Robots.Components
|
|
@using RobotNet.WebApp.Robots.Components.Traffic
|
|
|
|
@inject ISnackbar Snackbar
|
|
@inject TrafficHubClient TrafficHub
|
|
@inject IDialogService Dialog
|
|
@inject IHttpClientFactory HttpFactory
|
|
|
|
<PageTitle>Traffic Manager</PageTitle>
|
|
|
|
<style>
|
|
.selected {
|
|
background-color: #3399ff !important;
|
|
}
|
|
|
|
.selected > td {
|
|
color: white !important;
|
|
}
|
|
|
|
.selected > td .mud-input {
|
|
color: white !important;
|
|
}
|
|
</style>
|
|
|
|
<div class="d-flex flex-row w-100 h-100 p-2 overflow-hidden position-relative">
|
|
<div class="d-flex h-100 flex-column flex-grow-1 pe-2">
|
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
|
<MudTextField Value="txtSearch" T="string" Label="Search" Variant="Variant.Outlined" Adornment="Adornment.End" AdornmentIcon="@Icons.Material.Filled.Search"
|
|
AdornmentColor="Color.Secondary" Style="width: 40%; max-width: 400px" TextChanged="TextSearchChanged" />
|
|
<MudSelect @bind-Value="@MapSelected" Label="Maps" T="TrafficMapDto" Style="width: 30%; max-width: 300px" Variant="Variant.Outlined" @bind-Value:after="MapChanged">
|
|
@foreach (var map in Maps)
|
|
{
|
|
<MudSelectItem T="TrafficMapDto" Value="@map">@map.MapName</MudSelectItem>
|
|
}
|
|
</MudSelect>
|
|
@* <MudButton Class="ms-2" StartIcon="@Icons.Material.Filled.Add" Variant="Variant.Filled" Color="Color.Primary" Size="Size.Large">
|
|
Add Lock
|
|
</MudButton> *@
|
|
</div>
|
|
<div class="d-flex" style="height: 92%">
|
|
<MudTable Class="w-100" @ref="Table" T="TrafficAgentDto" Items="@AgentShow" Loading="@IsLoading" Dense Hover FixedHeader ReadOnly Striped RowClass="cursor-pointer"
|
|
ServerData="ReloadData" Outlined="true" Height="95%" RowClassFunc="@SelectedRowClassFunc" OnRowClick="RowClickEvent">
|
|
<HeaderContent>
|
|
<MudTh>Nr</MudTh>
|
|
<MudTh>RobotId</MudTh>
|
|
<MudTh>Traffic State</MudTh>
|
|
<MudTh>Target Node</MudTh>
|
|
<MudTh>Release Node</MudTh>
|
|
<MudTh>In Node</MudTh>
|
|
<MudTh>Locked Node</MudTh>
|
|
<MudTh></MudTh>
|
|
</HeaderContent>
|
|
<RowTemplate>
|
|
<MudTd DataLabel="Nr">
|
|
@(Table?.CurrentPage * Table?.RowsPerPage + AgentShow.IndexOf(context) + 1)
|
|
</MudTd>
|
|
<MudTd DataLabel="RobotId">
|
|
@context.RobotId
|
|
</MudTd>
|
|
<MudTd DataLabel="Traffic State">
|
|
@context.State
|
|
</MudTd>
|
|
<MudTd DataLabel="Target Node">
|
|
@(context.Nodes.Count > 0 ? (string.IsNullOrEmpty(context.Nodes[^1].Name) ? context.Nodes[^1].Id.ToString("N").Substring(0, 4) : context.Nodes[^1].Name) : "")
|
|
</MudTd>
|
|
<MudTd DataLabel="Release Node">
|
|
@(context.ReleaseNode is null ? "" : string.IsNullOrEmpty(context.ReleaseNode.Name) ? context.ReleaseNode.Id.ToString("N").Substring(0, 4) : context.ReleaseNode.Name)
|
|
</MudTd>
|
|
<MudTd DataLabel="In Node">
|
|
@(context.InNode is null ? "" : string.IsNullOrEmpty(context.InNode.Name) ? context.InNode.Id.ToString("N").Substring(0, 4) : context.InNode.Name)
|
|
</MudTd>
|
|
<MudTd DataLabel="Locked Node">
|
|
@(context.LockedNodes.Count > 0 ? (string.IsNullOrEmpty(context.LockedNodes[^1].Name) ? context.LockedNodes[^1].Id.ToString("N").Substring(0, 4) : context.LockedNodes[^1].Name) : "")
|
|
</MudTd>
|
|
<MudTd>
|
|
<div class="d-flex flex-row-reverse">
|
|
<MudMenu Icon="@Icons.Material.Filled.MoreVert" AnchorOrigin="Origin.BottomCenter">
|
|
<MudMenuItem Icon="@Icons.Material.Filled.Edit" IconColor="Color.Info" OnClick="@(async () => await OpenUpdateAgent(context))">Edit</MudMenuItem>
|
|
<MudMenuItem Icon="@Icons.Material.Filled.Delete" IconColor="Color.Error" OnClick="@(async () => await DeleteAgent(context))">Delete</MudMenuItem>
|
|
</MudMenu>
|
|
</div>
|
|
</MudTd>
|
|
</RowTemplate>
|
|
<PagerContent>
|
|
<div class="d-flex w-100 flex-row-reverse">
|
|
<MudTablePager Style="width: 100%;" PageSizeOptions="new[] {25 , 100, 200}" />
|
|
</div>
|
|
</PagerContent>
|
|
</MudTable>
|
|
</div>
|
|
</div>
|
|
<div class="agent-preview">
|
|
<TrafficAgentReview @ref="TrafficAgentReviewRef" />
|
|
</div>
|
|
<MudOverlay @bind-Visible="IsDeactive" DarkBackground="true" Absolute="true">
|
|
<MudText Typo="Typo.h1" Color="@Color.Error" Style="font-weight: bold;">@DeactiveStr</MudText>
|
|
</MudOverlay>
|
|
</div>
|
|
|
|
<MudDialog @bind-Visible="IsEditAgentVisible">
|
|
<TitleContent>
|
|
<MudText Typo="Typo.h6">
|
|
Edit Agent @AgentUpdate.AgentId
|
|
</MudText>
|
|
</TitleContent>
|
|
<DialogContent>
|
|
<MudContainer Class="d-flex flex-column">
|
|
<div class="d-flex flex-row">
|
|
<MudAutocomplete T="NodeDto" Variant="Variant.Outlined" Label="Node" SearchFunc="NodeSearch"
|
|
MaxItems="@Nodes.Count" ToStringFunc="@(e=> e == null ? null : $"{e.Name}")" ValueChanged="NodeChanged" ShowProgressIndicator="true">
|
|
<NoItemsTemplate>
|
|
<MudText Align="Align.Center" Class="px-4 py-1">
|
|
No items found
|
|
</MudText>
|
|
</NoItemsTemplate>
|
|
</MudAutocomplete>
|
|
<MudButton Class="d-flex flex-grow-1 mt-2 mb-1 ms-2" Variant="Variant.Filled" Color="Color.Info" OnClick="AddNode">Add</MudButton>
|
|
</div>
|
|
|
|
<div class="paper-nodes">
|
|
@foreach (var node in AgentUpdate.LockedNodes)
|
|
{
|
|
<div class="m-1" style="height: fit-content">
|
|
<MudButton Variant="Variant.Filled" Color="Color.Info" Style="text-transform:none" OnClick="@(() => AgentUpdate.LockedNodes.Remove(node))">
|
|
@(string.IsNullOrEmpty(node.Name) ? node.Id.ToString("N").Substring(0, 4) : node.Name)
|
|
</MudButton>
|
|
</div>
|
|
}
|
|
</div>
|
|
</MudContainer>
|
|
</DialogContent>
|
|
<DialogActions>
|
|
<MudButton OnClick="@(() => IsEditAgentVisible = false)" Variant="Variant.Filled">Cancel</MudButton>
|
|
<MudButton Color="Color.Primary" OnClick="UpdateAgent" Variant="Variant.Filled">Update</MudButton>
|
|
</DialogActions>
|
|
</MudDialog>
|
|
|
|
@code {
|
|
private string? txtSearch { get; set; }
|
|
private bool IsLoading { get; set; }
|
|
|
|
private bool IsDeactive;
|
|
private string DeactiveStr = "Robot Empty";
|
|
|
|
private MudTable<TrafficAgentDto>? Table;
|
|
private readonly List<TrafficAgentDto> Agents = [];
|
|
private List<TrafficAgentDto> AgentShow = [];
|
|
|
|
private int selectedRowNumber = -1;
|
|
private TrafficAgentDto? AgentSelected;
|
|
|
|
private List<TrafficMapDto> Maps = [];
|
|
private TrafficMapDto? MapSelected;
|
|
private TrafficAgentReview TrafficAgentReviewRef = default!;
|
|
|
|
private bool IsEditAgentVisible;
|
|
private readonly UpdateAgentLockerModel AgentUpdate = new();
|
|
private List<NodeDto> Nodes = [];
|
|
private NodeDto? SelectedNode;
|
|
|
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
|
{
|
|
await base.OnAfterRenderAsync(firstRender);
|
|
if (!firstRender) return;
|
|
|
|
TrafficHub.TrafficAgentUpdated += TrafficAgentUpdated;
|
|
TrafficHub.MapDeactive += MapDeactive;
|
|
await TrafficHub.StartAsync();
|
|
await LoadTrafficMaps();
|
|
}
|
|
|
|
private async Task LoadTrafficMaps()
|
|
{
|
|
IsLoading = true;
|
|
StateHasChanged();
|
|
|
|
var trafficMaps = await TrafficHub.LoadTrafficMaps();
|
|
if (!trafficMaps.IsSuccess) Snackbar.Add(trafficMaps.Message, Severity.Warning);
|
|
else if (trafficMaps.Data is null || !trafficMaps.Data.Any())
|
|
{
|
|
Snackbar.Add("Không có bản đồ nào được tải lên", Severity.Warning);
|
|
IsLoading = false;
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
Maps = [.. trafficMaps.Data];
|
|
if (Maps.Count > 0)
|
|
{
|
|
MapSelected = Maps.First();
|
|
Agents.Clear();
|
|
Agents.AddRange(MapSelected.Agents);
|
|
Table?.ReloadServerData();
|
|
await TrafficHub.TrafficActive(MapSelected.MapId);
|
|
}
|
|
}
|
|
|
|
IsLoading = false;
|
|
StateHasChanged();
|
|
}
|
|
|
|
private void TextSearchChanged(string text)
|
|
{
|
|
txtSearch = text;
|
|
Table?.ReloadServerData();
|
|
}
|
|
|
|
private bool FilterFunc(TrafficAgentDto robot)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(txtSearch))
|
|
return true;
|
|
if (robot.RobotId.Contains(txtSearch, StringComparison.OrdinalIgnoreCase))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
private Task<TableData<TrafficAgentDto>> ReloadData(TableState state, CancellationToken _)
|
|
{
|
|
AgentShow.Clear();
|
|
var agents = new List<TrafficAgentDto>();
|
|
Agents.ForEach(agent =>
|
|
{
|
|
if (FilterFunc(agent)) agents.Add(agent);
|
|
});
|
|
AgentShow = agents.Skip(state.Page * state.PageSize).Take(state.PageSize).ToList();
|
|
return Task.FromResult(new TableData<TrafficAgentDto>() { TotalItems = agents.Count(), Items = AgentShow });
|
|
}
|
|
|
|
private void RowClickEvent(TableRowClickEventArgs<TrafficAgentDto> tableRowClickEventArgs) { }
|
|
|
|
private string SelectedRowClassFunc(TrafficAgentDto element, int rowNumber)
|
|
{
|
|
if (selectedRowNumber == rowNumber && Table?.SelectedItem != null && !Table.SelectedItem.Equals(element))
|
|
{
|
|
return string.Empty;
|
|
}
|
|
else if (selectedRowNumber == rowNumber && Table?.SelectedItem != null && Table.SelectedItem.Equals(element))
|
|
{
|
|
return "selected";
|
|
}
|
|
else if (Table?.SelectedItem != null && Table.SelectedItem.Equals(element))
|
|
{
|
|
selectedRowNumber = rowNumber;
|
|
AgentSelected = element;
|
|
TrafficAgentReviewRef.UpdateState(AgentSelected);
|
|
return "selected";
|
|
}
|
|
else
|
|
{
|
|
return string.Empty;
|
|
}
|
|
}
|
|
|
|
private async Task MapChanged()
|
|
{
|
|
if (MapSelected is not null)
|
|
{
|
|
var trafficMap = await TrafficHub.LoadTrafficMap(MapSelected.MapId);
|
|
if (!trafficMap.IsSuccess) Snackbar.Add(trafficMap.Message, Severity.Warning);
|
|
else if (trafficMap.Data is null)
|
|
{
|
|
Snackbar.Add("Không có agent nào trong bản đồ này", Severity.Warning);
|
|
Agents.Clear();
|
|
AgentShow.Clear();
|
|
if (Table is not null) await Table.ReloadServerData();
|
|
}
|
|
else
|
|
{
|
|
Agents.Clear();
|
|
Agents.AddRange(trafficMap.Data.Agents);
|
|
if (Table is not null) await Table.ReloadServerData();
|
|
await TrafficHub.TrafficActive(MapSelected.MapId);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void TrafficAgentUpdated(IEnumerable<TrafficAgentDto> agents)
|
|
{
|
|
bool isNewUpdate = false;
|
|
foreach (var newagent in agents)
|
|
{
|
|
var agent = Agents.FirstOrDefault(a => a.RobotId == newagent.RobotId);
|
|
if (agent is null)
|
|
{
|
|
isNewUpdate = true;
|
|
Agents.Add(newagent);
|
|
}
|
|
else
|
|
{
|
|
if ((agent.ReleaseNode is null && newagent.ReleaseNode is not null) || (agent.ReleaseNode is not null && newagent.ReleaseNode is null) ||
|
|
(agent.ReleaseNode is not null && newagent.ReleaseNode is not null && agent.ReleaseNode.Id != newagent.ReleaseNode.Id) ||
|
|
(agent.InNode is null && newagent.InNode is not null) || (agent.InNode is not null && newagent.InNode is null) ||
|
|
(agent.InNode is not null && newagent.InNode is not null && agent.InNode.Id != newagent.InNode.Id) ||
|
|
agent.State != newagent.State ||
|
|
(agent.Nodes.Count > 0 && newagent.Nodes.Count == 0) || (agent.Nodes.Count == 0 && newagent.Nodes.Count > 0) ||
|
|
(agent.Nodes.Count > 0 && newagent.Nodes.Count > 0 && agent.Nodes[^1].Id != newagent.Nodes[^1].Id) ||
|
|
(agent.LockedNodes.Count > 0 && newagent.LockedNodes.Count == 0) || (agent.LockedNodes.Count == 0 && newagent.LockedNodes.Count > 0) ||
|
|
(agent.LockedNodes.Count > 0 && newagent.LockedNodes.Count > 0 && agent.LockedNodes[^1].Id != newagent.LockedNodes[^1].Id)
|
|
) isNewUpdate = true;
|
|
agent.Nodes = [.. newagent.Nodes];
|
|
agent.ReleaseNode = newagent.ReleaseNode;
|
|
agent.LockedNodes = [.. newagent.LockedNodes];
|
|
agent.InNode = newagent.InNode;
|
|
agent.State = newagent.State;
|
|
agent.SubNodes = [.. newagent.SubNodes];
|
|
agent.GiveWayNodes = [.. newagent.GiveWayNodes];
|
|
agent.ConflictNode = newagent.ConflictNode;
|
|
agent.ConflictAgentId = newagent.ConflictAgentId;
|
|
}
|
|
if (AgentSelected is not null && newagent.RobotId == AgentSelected.RobotId) TrafficAgentReviewRef.UpdateState(AgentSelected);
|
|
}
|
|
if (isNewUpdate) Table?.ReloadServerData();
|
|
}
|
|
|
|
private void MapDeactive()
|
|
{
|
|
DeactiveStr = "Deactive";
|
|
IsDeactive = true;
|
|
StateHasChanged();
|
|
TrafficHub.TrafficAgentUpdated -= TrafficAgentUpdated;
|
|
TrafficHub.MapDeactive -= MapDeactive;
|
|
_ = TrafficHub.StopAsync();
|
|
}
|
|
|
|
private async Task DeleteAgent(TrafficAgentDto agent)
|
|
{
|
|
var parameters = new DialogParameters<ConfirmDialog>
|
|
{
|
|
{ x => x.Content, "Bạn có chắc chắn muốn xóa agent đi không?" },
|
|
{ x => x.ConfirmText, "Delete" },
|
|
{ x => x.Color, Color.Secondary }
|
|
};
|
|
var ConfirmDelete = await Dialog.ShowAsync<ConfirmDialog>("Xoá Agent", parameters);
|
|
var result = await ConfirmDelete.Result;
|
|
if (result is not null && result.Data is not null && bool.TryParse(result.Data.ToString(), out bool data) && data)
|
|
{
|
|
using var Http = HttpFactory.CreateClient("RobotManagerAPI");
|
|
var delete = await Http.DeleteFromJsonAsync<MessageResult>($"api/TrafficManager/{MapSelected?.MapId}/{agent.RobotId}");
|
|
if (delete is null) Snackbar.Add("Lỗi giao tiếp với hệ thống", Severity.Error);
|
|
else if (!delete.IsSuccess) Snackbar.Add($"Có lỗi xảy ra: {delete.Message}", Severity.Error);
|
|
else
|
|
{
|
|
Agents.Remove(agent);
|
|
Snackbar.Add("Xóa robot thành công!", Severity.Success);
|
|
Table?.ReloadServerData();
|
|
}
|
|
StateHasChanged();
|
|
}
|
|
}
|
|
|
|
private async Task OpenUpdateAgent(TrafficAgentDto agent)
|
|
{
|
|
AgentUpdate.AgentId = agent.RobotId;
|
|
AgentUpdate.LockedNodes = agent.LockedNodes;
|
|
AgentUpdate.MapId = MapSelected is null ? Guid.Empty : MapSelected.MapId;
|
|
await LoadNodes();
|
|
IsEditAgentVisible = true;
|
|
StateHasChanged();
|
|
}
|
|
|
|
private async Task UpdateAgent()
|
|
{
|
|
using var Http = HttpFactory.CreateClient("RobotManagerAPI");
|
|
var result = await (await Http.PutAsJsonAsync($"api/TrafficManager", AgentUpdate)).Content.ReadFromJsonAsync<MessageResult>();
|
|
if (result == null) Snackbar.Add("Lỗi giao tiếp với hệ thống", Severity.Error);
|
|
else if (!result.IsSuccess) Snackbar.Add(result.Message, Severity.Error);
|
|
else Snackbar.Add("Cập nhật thành công", Severity.Success);
|
|
|
|
IsEditAgentVisible = false;
|
|
StateHasChanged();
|
|
}
|
|
|
|
public async Task LoadNodes()
|
|
{
|
|
try
|
|
{
|
|
using var Http = HttpFactory.CreateClient("MapManagerAPI");
|
|
var result = await Http.GetFromJsonAsync<NodeDto[]>($"api/Nodes/{MapSelected?.MapId}");
|
|
if (result is not null) Nodes = result.Where(n => !string.IsNullOrEmpty(n.Name)).ToList();
|
|
}
|
|
catch (AccessTokenNotAvailableException ex)
|
|
{
|
|
ex.Redirect();
|
|
}
|
|
}
|
|
|
|
private async Task<IEnumerable<NodeDto>> NodeSearch(string value, CancellationToken token)
|
|
{
|
|
await Task.Delay(5, token);
|
|
|
|
if (string.IsNullOrEmpty(value))
|
|
{
|
|
return Nodes.Select(n => n);
|
|
}
|
|
|
|
return Nodes.Where(x => x.Name.Contains(value, StringComparison.InvariantCultureIgnoreCase)).Select(n => n);
|
|
}
|
|
|
|
private void NodeChanged(NodeDto value)
|
|
{
|
|
if (SelectedNode is null || value.Id != SelectedNode.Id) SelectedNode = value;
|
|
}
|
|
|
|
private void AddNode()
|
|
{
|
|
if (SelectedNode is not null && !AgentUpdate.LockedNodes.Any(n => n.Id == SelectedNode.Id)) AgentUpdate.LockedNodes.Add(new()
|
|
{
|
|
Id = SelectedNode.Id,
|
|
X = SelectedNode.X,
|
|
Y = SelectedNode.Y,
|
|
Name = SelectedNode.Name,
|
|
});
|
|
StateHasChanged();
|
|
}
|
|
|
|
public async ValueTask DisposeAsync()
|
|
{
|
|
TrafficHub.TrafficAgentUpdated -= TrafficAgentUpdated;
|
|
TrafficHub.MapDeactive -= MapDeactive;
|
|
await TrafficHub.StopAsync();
|
|
}
|
|
}
|