Initial commit
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
@page "/config-manager"
|
||||
@rendermode RobotNet10.Components.RenderMode.InteractiveWebAssemblyNoPrerender
|
||||
|
||||
<PageTitle>Configuration Manager</PageTitle>
|
||||
|
||||
<RobotNet10.CustomConfigurationEditor.Components.ConfigManager.ConfigManagerComponent />
|
||||
|
||||
<MudThemeProvider IsDarkMode />
|
||||
<MudPopoverProvider />
|
||||
<MudDialogProvider />
|
||||
<MudSnackbarProvider />
|
||||
@@ -0,0 +1,16 @@
|
||||
@page "/layout-editor/{LevelId:guid}"
|
||||
@rendermode RobotNet10.Components.RenderMode.InteractiveWebAssemblyNoPrerender
|
||||
|
||||
<PageTitle>Layout Editor</PageTitle>
|
||||
|
||||
<RobotNet10.MapEditor.Components.LayoutEditor.LayoutEditorComponent LevelId="@LevelId" />
|
||||
|
||||
<MudThemeProvider IsDarkMode />
|
||||
<MudPopoverProvider />
|
||||
<MudDialogProvider />
|
||||
<MudSnackbarProvider />
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public Guid LevelId { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
@page "/layout-manager"
|
||||
@rendermode InteractiveWebAssemblyNoPrerender
|
||||
|
||||
<PageTitle>Layout Manager</PageTitle>
|
||||
|
||||
<RobotNet10.MapEditor.Components.LayoutManager.LayoutManagerComponent />
|
||||
|
||||
<MudThemeProvider IsDarkMode />
|
||||
<MudPopoverProvider />
|
||||
<MudDialogProvider />
|
||||
<MudSnackbarProvider />
|
||||
@@ -0,0 +1,225 @@
|
||||
@page "/logs"
|
||||
@rendermode InteractiveWebAssemblyNoPrerender
|
||||
|
||||
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
|
||||
@using System.Text.Json.Serialization
|
||||
|
||||
@inject IJSRuntime JSRuntime
|
||||
@inject HttpClient Http
|
||||
@inject IConfiguration Configuration
|
||||
@inject ISnackbar Snackbar
|
||||
|
||||
<PageTitle>Logs</PageTitle>
|
||||
|
||||
<div class="w-100 h-100 d-flex flex-column">
|
||||
<div class="d-flex flex-row align-items-center justify-content-between" style="border-bottom: 1px solid silver">
|
||||
<MudTextField Class="mt-1 ms-2" T="string" Value="FilterLog" Adornment="Adornment.End" ValueChanged="OnSearch" AdornmentIcon="@Icons.Material.Filled.Search"
|
||||
IconSize="Size.Medium" Variant="Variant.Outlined" Margin="Margin.Dense" AdornmentColor="Color.Secondary" Label="Search"></MudTextField>
|
||||
<MudSpacer />
|
||||
<div class="m-1 d-flex flex-row">
|
||||
<MudDatePicker Class="mx-4" Label="Date" Date="DateLog" DateChanged="OnDateChanged" MaxDate="DateTime.Today" Variant="Variant.Outlined" Color="Color.Primary"
|
||||
ShowToolbar="false" Margin="Margin.Dense" AdornmentColor="Color.Primary" />
|
||||
|
||||
<MudTooltip Text="Export">
|
||||
<MudFab Class="mt-2" Color="Color.Info" StartIcon="@Icons.Material.Filled.ImportExport" Size="Size.Small" OnClick="ExportLogs" />
|
||||
</MudTooltip>
|
||||
<MudTooltip Text="Refresh">
|
||||
<MudFab Class="mx-4 mt-2" StartIcon="@Icons.Material.Filled.Refresh" Color="Color.Primary" Size="Size.Small" OnClick="LoadLogs" />
|
||||
</MudTooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-grow-1 mt-2 ms-2 position-relative" style="background-color: rgba(0, 0, 0, 0);">
|
||||
<MudOverlay Visible="IsLoading" DarkBackground="true" Absolute="true">
|
||||
<MudProgressCircular Color="Color.Info" Indeterminate="true" />
|
||||
</MudOverlay>
|
||||
<div class="h-100 w-100 position-relative">
|
||||
<div class="log-container" @ref="LogContainerRef">
|
||||
@if (ShowRawLog)
|
||||
{
|
||||
<div class="d-flex justify-content-center my-3">
|
||||
<div><MudButton Variant="Variant.Outlined" Size="Size.Small" OnClick="@(() => ShowRawLog = false)">Normal</MudButton></div>
|
||||
</div>
|
||||
<big style="font-size: 14px;">
|
||||
@foreach (var log in ShowLogs)
|
||||
{
|
||||
@log <br />
|
||||
}
|
||||
</big>
|
||||
}
|
||||
else
|
||||
{
|
||||
@if (SearchLogs.Count < ShowLogs.Count)
|
||||
{
|
||||
<div class="d-flex justify-content-center my-3">
|
||||
<div><MudButton Variant="Variant.Outlined" Size="Size.Small" OnClick="@(() => ShowRawLog = true)">Raw log</MudButton></div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@foreach (var log in SearchLogs)
|
||||
{
|
||||
<div class="log">
|
||||
<span class="log-head @log.BackgroundClass">
|
||||
@log.Time <span class="log-level">@log.Level</span>
|
||||
</span>
|
||||
<span>@log.Message</span>
|
||||
@if (log.HasException)
|
||||
{
|
||||
<br />
|
||||
<pre class="log-exception">
|
||||
@log.Exception
|
||||
</pre>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
window.ScrollToBottom = (element) => {
|
||||
if (element) {
|
||||
element.scrollTop = element.scrollHeight;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<MudThemeProvider IsDarkMode />
|
||||
<MudPopoverProvider />
|
||||
<MudDialogProvider />
|
||||
<MudSnackbarProvider />
|
||||
|
||||
@code {
|
||||
private DateTime DateLog = DateTime.Today;
|
||||
private bool IsLoading;
|
||||
private readonly List<string> ShowLogs = new();
|
||||
private readonly List<LoggerModel> SearchLogs = new();
|
||||
private ElementReference LogContainerRef { get; set; }
|
||||
private bool ShowRawLog { get; set; }
|
||||
private string? FilterLog { get; set; }
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
await base.OnAfterRenderAsync(firstRender);
|
||||
if (!firstRender) return;
|
||||
|
||||
await LoadLogs();
|
||||
}
|
||||
|
||||
private async Task LoadLogs()
|
||||
{
|
||||
try
|
||||
{
|
||||
IsLoading = true;
|
||||
ShowLogs.Clear();
|
||||
StateHasChanged();
|
||||
|
||||
var logs = await Http.GetFromJsonAsync<IEnumerable<string>>($"api/LogsManager?date={DateLog}");
|
||||
ShowLogs.AddRange(logs ?? []);
|
||||
|
||||
IsLoading = false;
|
||||
StateHasChanged();
|
||||
|
||||
await ReloadLogs();
|
||||
}
|
||||
catch (AccessTokenNotAvailableException ex)
|
||||
{
|
||||
ex.Redirect();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ReloadLogs()
|
||||
{
|
||||
IsLoading = true;
|
||||
SearchLogs.Clear();
|
||||
StateHasChanged();
|
||||
|
||||
foreach (var line in ShowLogs.Where(log => string.IsNullOrEmpty(FilterLog) || log.Contains(FilterLog)).TakeLast(2000))
|
||||
{
|
||||
try
|
||||
{
|
||||
var log = System.Text.Json.JsonSerializer.Deserialize<LoggerModel>(line);
|
||||
if (log is not null) SearchLogs.Add(log);
|
||||
}
|
||||
catch (System.Text.Json.JsonException)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
IsLoading = false;
|
||||
StateHasChanged();
|
||||
await JSRuntime.InvokeVoidAsync("ScrollToBottom", LogContainerRef);
|
||||
}
|
||||
|
||||
private async Task OnSearch(string text)
|
||||
{
|
||||
FilterLog = text;
|
||||
await ReloadLogs();
|
||||
}
|
||||
|
||||
private async Task OnDateChanged(DateTime? date)
|
||||
{
|
||||
if (date is not null && date.HasValue)
|
||||
{
|
||||
DateLog = date.Value;
|
||||
await LoadLogs();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ExportLogs()
|
||||
{
|
||||
try
|
||||
{
|
||||
var fileContent = await Http.GetFromJsonAsync<IEnumerable<string>>($"api/LogsManager?date={DateLog}");
|
||||
var formattedContent = string.Join("\n", fileContent ?? []);
|
||||
var fileName = $"LogsManager_{DateLog.ToShortDateString()}.txt";
|
||||
await JSRuntime.InvokeVoidAsync("downloadFile", fileName, formattedContent, "text/plain");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"Lỗi khi tải file: {ex.Message}", Severity.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
public class LoggerModel
|
||||
{
|
||||
[JsonPropertyName("time")]
|
||||
public string? Time { get; set; }
|
||||
|
||||
[JsonPropertyName("level")]
|
||||
public string? Level { get; set; }
|
||||
|
||||
[JsonPropertyName("message")]
|
||||
public string? Message { get; set; }
|
||||
|
||||
[JsonPropertyName("exception")]
|
||||
public string? Exception { get; set; }
|
||||
|
||||
public string ColorClass => Level switch
|
||||
{
|
||||
"WARN" => "text-warning",
|
||||
"INFO" => "text-info",
|
||||
"DEBUG" => "text-success",
|
||||
"ERROR" => "text-danger",
|
||||
"FATAL" => "text-secondary",
|
||||
_ => "text-muted",
|
||||
};
|
||||
|
||||
public string BackgroundClass => Level switch
|
||||
{
|
||||
"WARN" => "bg-warning text-dark",
|
||||
"INFO" => "bg-info text-dark",
|
||||
"DEBUG" => "bg-success text-white",
|
||||
"ERROR" => "bg-danger text-white",
|
||||
"FATAL" => "bg-secondary text-white",
|
||||
_ => "bg-dark text-white",
|
||||
};
|
||||
|
||||
public bool HasException => !string.IsNullOrEmpty(Exception);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
.log-container {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.log {
|
||||
word-wrap: break-word;
|
||||
line-height: 18px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.log-logger {
|
||||
color: rgba(0, 0, 0, 0.3);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.log-level {
|
||||
display: inline-block;
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.log-head {
|
||||
border-radius: 3px;
|
||||
padding: 2px 5px;
|
||||
}
|
||||
|
||||
.log-exception {
|
||||
line-height: 16px;
|
||||
margin-left: 30px;
|
||||
color: crimson;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
@page "/missions"
|
||||
@rendermode InteractiveWebAssemblyNoPrerender
|
||||
|
||||
<PageTitle>Mission Manager</PageTitle>
|
||||
|
||||
<RobotNet10.ScriptEditor.InstanceMissionManager />
|
||||
|
||||
<MudThemeProvider IsDarkMode />
|
||||
<MudPopoverProvider />
|
||||
<MudDialogProvider />
|
||||
<MudSnackbarProvider />
|
||||
@@ -0,0 +1,281 @@
|
||||
@page "/robots/{robotId}/detail"
|
||||
@rendermode RobotNet10.Components.RenderMode.InteractiveWebAssemblyNoPrerender
|
||||
@implements IAsyncDisposable
|
||||
|
||||
<PageTitle>Robot Detail - @robotName</PageTitle>
|
||||
|
||||
@using RobotNet10.FleetManager.Client.Services
|
||||
@using RobotNet10.FleetManager.Client.Components.RobotDetail
|
||||
@using RobotNet10.FleetManager.Shared.DTOs.Robot
|
||||
@using RobotNet.VDA5050.State
|
||||
@using RobotNet.VDA5050.Visualization
|
||||
@using Microsoft.AspNetCore.SignalR.Client
|
||||
@inject RobotApiService RobotApiService
|
||||
@inject RobotStateHubClient HubClient
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject ISnackbar Snackbar
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraExtraLarge" Class="mt-4">
|
||||
<!-- Header -->
|
||||
<MudPaper Class="pa-4 mb-4 align-content-center" MinHeight="80px">
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween" Spacing="3">
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.ArrowBack" OnClick="NavigateBack" />
|
||||
<MudText Typo="Typo.h5">Robot Detail: @robotName</MudText>
|
||||
</MudStack>
|
||||
<MudButton StartIcon="@Icons.Material.Filled.Refresh"
|
||||
Variant="Variant.Outlined"
|
||||
Color="Color.Primary"
|
||||
OnClick="HandleRefresh"
|
||||
Disabled="@isRefreshing">
|
||||
@if (isRefreshing)
|
||||
{
|
||||
<MudProgressCircular Class="mr-2" Size="Size.Small" Indeterminate="true" />
|
||||
}
|
||||
Refresh
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
|
||||
@if (isLoadingRobot)
|
||||
{
|
||||
<MudPaper Class="pa-4" Elevation="1">
|
||||
<MudStack AlignItems="AlignItems.Center" Spacing="3">
|
||||
<MudProgressCircular Color="Color.Primary" Indeterminate="true" Size="Size.Large" />
|
||||
<MudText Typo="Typo.body1">Loading robot information...</MudText>
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
}
|
||||
else if (robot == null)
|
||||
{
|
||||
<MudPaper Class="pa-4" Elevation="1">
|
||||
<MudAlert Severity="Severity.Error" Variant="Variant.Filled">
|
||||
<MudText Typo="Typo.h6">Robot Not Found</MudText>
|
||||
<MudText Typo="Typo.body2">The robot with ID '@RobotId' could not be found.</MudText>
|
||||
<MudButton Variant="Variant.Text" Color="Color.Error" OnClick="NavigateBack" Class="mt-2">
|
||||
<MudIcon Icon="@Icons.Material.Filled.ArrowBack" Class="mr-2" />
|
||||
Go Back to Robot List
|
||||
</MudButton>
|
||||
</MudAlert>
|
||||
</MudPaper>
|
||||
}
|
||||
else
|
||||
{
|
||||
<!-- Main Content -->
|
||||
<MudGrid>
|
||||
<!-- Left: State Cards with Tabs (60%) -->
|
||||
<MudItem xs="12" md="7">
|
||||
<MudPaper Class="pa-2" Elevation="1" Style="height: calc(100vh - 200px); overflow: hidden">
|
||||
<MudTabs @bind-ActivePanelIndex="ActivePanelIndex" Elevation="0" Rounded="true" Color="Color.Primary">
|
||||
<!-- Overview Tab -->
|
||||
<MudTabPanel Text="Overview" Icon="@Icons.Material.Filled.Dashboard">
|
||||
<MudStack Spacing="3" Class="mt-2" Style="overflow-y: auto; height: calc(100vh - 288px);">
|
||||
<HeaderInfoCard @ref="HeaderInfoCardRef" />
|
||||
|
||||
<MudGrid Spacing="3">
|
||||
<MudItem xs="12" md="6">
|
||||
<PositionCard @ref="PositionCardRef" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="6">
|
||||
<VelocityCard @ref="VelocityCardRef" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
|
||||
<MudGrid Spacing="3">
|
||||
<MudItem xs="12" md="6">
|
||||
<BatteryCard @ref="BatteryCardRef" />
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="6">
|
||||
<SafetyStateCard @ref="SafetyStateCardRef" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudStack>
|
||||
</MudTabPanel>
|
||||
|
||||
<!-- Navigation Tab -->
|
||||
<MudTabPanel Text="Navigation" Icon="@Icons.Material.Filled.Navigation">
|
||||
<MudStack Spacing="3" Class="mt-2" Style="overflow-y: auto; height: calc(100vh - 288px);">
|
||||
<MapsCard @ref="MapsCardRef" />
|
||||
<NodeStatesCard @ref="NodeStatesCardRef" />
|
||||
<EdgeStatesCard @ref="EdgeStatesCardRef" />
|
||||
</MudStack>
|
||||
</MudTabPanel>
|
||||
|
||||
<!-- Orders & Actions Tab -->
|
||||
<MudTabPanel Text="Orders & Actions" Icon="@Icons.Material.Filled.ListAlt">
|
||||
<MudStack Spacing="3" Class="mt-2" Style="overflow-y: auto; height: calc(100vh - 288px);">
|
||||
<OrderInfoCard @ref="OrderInfoCardRef" />
|
||||
<ActionStatesCard @ref="ActionStatesCardRef" />
|
||||
<LoadsCard @ref="LoadsCardRef" />
|
||||
</MudStack>
|
||||
</MudTabPanel>
|
||||
|
||||
<!-- Errors & Information Tab -->
|
||||
<MudTabPanel Text="Errors & Info" Icon="@Icons.Material.Filled.Info">
|
||||
<MudStack Spacing="3" Class="mt-2" Style="overflow-y: auto; height: calc(100vh - 288px);">
|
||||
<ErrorsCard @ref="ErrorsCardRef" />
|
||||
<InformationCard @ref="InformationCardRef" />
|
||||
</MudStack>
|
||||
</MudTabPanel>
|
||||
</MudTabs>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
|
||||
<!-- Right: Manual Actions & Orders (40%) -->
|
||||
<MudItem xs="12" md="5">
|
||||
<MudStack Spacing="3" Style="max-height: calc(100vh - 200px); overflow-y: auto;">
|
||||
<MudPaper Class="pa-2" Elevation="2">
|
||||
<ManualOrderPanel RobotId="@RobotId" MapId="@robot?.MapId" />
|
||||
</MudPaper>
|
||||
<MudPaper Class="pa-2" Elevation="2">
|
||||
<ManualActionsPanel RobotId="@RobotId" />
|
||||
</MudPaper>
|
||||
</MudStack>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
}
|
||||
</MudContainer>
|
||||
|
||||
<MudThemeProvider IsDarkMode />
|
||||
<MudPopoverProvider />
|
||||
<MudDialogProvider />
|
||||
<MudSnackbarProvider />
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public string RobotId { get; set; } = string.Empty;
|
||||
|
||||
private RobotDto? robot;
|
||||
private string robotName = "Loading...";
|
||||
private bool isLoadingRobot = true;
|
||||
private bool isRefreshing = false;
|
||||
|
||||
private HeaderInfoCard HeaderInfoCardRef = default!;
|
||||
private PositionCard PositionCardRef = default!;
|
||||
private VelocityCard VelocityCardRef = default!;
|
||||
private BatteryCard BatteryCardRef = default!;
|
||||
private SafetyStateCard SafetyStateCardRef = default!;
|
||||
private MapsCard MapsCardRef = default!;
|
||||
private NodeStatesCard NodeStatesCardRef = default!;
|
||||
private EdgeStatesCard EdgeStatesCardRef = default!;
|
||||
private OrderInfoCard OrderInfoCardRef = default!;
|
||||
private ActionStatesCard ActionStatesCardRef = default!;
|
||||
private LoadsCard LoadsCardRef = default!;
|
||||
private ErrorsCard ErrorsCardRef = default!;
|
||||
private InformationCard InformationCardRef = default!;
|
||||
|
||||
private int ActivePanelIndex = 0;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await LoadRobotAsync();
|
||||
await InitializeSignalRAsync();
|
||||
}
|
||||
|
||||
private async Task LoadRobotAsync()
|
||||
{
|
||||
isLoadingRobot = true;
|
||||
try
|
||||
{
|
||||
robot = await RobotApiService.GetByRobotIdAsync(RobotId);
|
||||
if (robot != null)
|
||||
{
|
||||
robotName = robot.Name;
|
||||
}
|
||||
else
|
||||
{
|
||||
Snackbar.Add($"Robot with ID '{RobotId}' not found", Severity.Error);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"Error loading robot: {ex.Message}", Severity.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
isLoadingRobot = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task InitializeSignalRAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Register event handlers
|
||||
HubClient.OnStateUpdate += HandleStateUpdate;
|
||||
HubClient.OnConnectionError += HandleConnectionError;
|
||||
|
||||
// Connect and subscribe
|
||||
await HubClient.ConnectAsync();
|
||||
await HubClient.SubscribeToRobotAsync(RobotId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"Error connecting to SignalR: {ex.Message}", Severity.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void StateUpdate(StateMsg? state)
|
||||
{
|
||||
switch (ActivePanelIndex)
|
||||
{
|
||||
case 0:
|
||||
HeaderInfoCardRef.Update(state);
|
||||
BatteryCardRef.Update(state?.BatteryState);
|
||||
SafetyStateCardRef.Update(state);
|
||||
PositionCardRef.Update(state);
|
||||
VelocityCardRef.Update(state);
|
||||
break;
|
||||
case 1:
|
||||
MapsCardRef.Update(state);
|
||||
NodeStatesCardRef.Update(state);
|
||||
EdgeStatesCardRef.Update(state);
|
||||
break;
|
||||
case 2:
|
||||
OrderInfoCardRef.Update(state);
|
||||
ActionStatesCardRef.Update(state);
|
||||
LoadsCardRef.Update(state);
|
||||
break;
|
||||
case 3:
|
||||
ErrorsCardRef.Update(state?.Errors);
|
||||
InformationCardRef.Update(state?.Information);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleStateUpdate(StateMsg state)
|
||||
{
|
||||
if (state.SerialNumber != RobotId) return;
|
||||
StateUpdate(state);
|
||||
}
|
||||
|
||||
private void HandleConnectionError(string error)
|
||||
{
|
||||
Snackbar.Add($"SignalR connection error: {error}", Severity.Warning);
|
||||
}
|
||||
|
||||
private async Task HandleRefresh()
|
||||
{
|
||||
isRefreshing = true;
|
||||
StateHasChanged();
|
||||
await LoadRobotAsync();
|
||||
isRefreshing = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private void NavigateBack()
|
||||
{
|
||||
NavigationManager.NavigateTo("/robots");
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
if (HubClient != null)
|
||||
{
|
||||
HubClient.OnStateUpdate -= HandleStateUpdate;
|
||||
HubClient.OnConnectionError -= HandleConnectionError;
|
||||
await HubClient.UnsubscribeFromRobotAsync(RobotId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
@page "/robots"
|
||||
@rendermode RobotNet10.Components.RenderMode.InteractiveWebAssemblyNoPrerender
|
||||
|
||||
<PageTitle>Robot Management</PageTitle>
|
||||
|
||||
@using RobotNet10.FleetManager.Client.Services
|
||||
@using RobotNet10.FleetManager.Client.Components.RobotManager
|
||||
@using RobotNet10.FleetManager.Client.Components.RobotManager.Dialogs
|
||||
@using RobotNet10.FleetManager.Shared.DTOs.Robot
|
||||
@inject RobotApiService RobotApiService
|
||||
@inject RobotModelApiService RobotModelApiService
|
||||
@inject IDialogService DialogService
|
||||
@inject ISnackbar Snackbar
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraExtraLarge" Class="mt-4">
|
||||
<!-- Header -->
|
||||
<MudPaper Class="pa-4 mb-4 align-content-center" MinHeight="80px">
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween" Spacing="3">
|
||||
<MudText Typo="Typo.h5">Robot Management</MudText>
|
||||
<MudButton StartIcon="@Icons.Material.Filled.Add"
|
||||
Variant="Variant.Filled"
|
||||
Color="Color.Success"
|
||||
OnClick="HandleCreate">
|
||||
Add Robot
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
|
||||
<!-- Main Content -->
|
||||
<RobotTable @ref="TableRef"
|
||||
RobotApiService="@RobotApiService"
|
||||
RobotModelApiService="@RobotModelApiService"
|
||||
OnRobotDeleted="@HandleDeleted"
|
||||
OnRobotUpdated="@HandleUpdated" />
|
||||
</MudContainer>
|
||||
|
||||
<MudThemeProvider IsDarkMode />
|
||||
<MudPopoverProvider />
|
||||
<MudDialogProvider />
|
||||
<MudSnackbarProvider />
|
||||
|
||||
@code {
|
||||
private RobotTable? TableRef;
|
||||
|
||||
private async Task HandleCreate()
|
||||
{
|
||||
var parameters = new DialogParameters
|
||||
{
|
||||
["RobotApiService"] = RobotApiService,
|
||||
["RobotModelApiService"] = RobotModelApiService
|
||||
};
|
||||
|
||||
var dialog = await DialogService.ShowAsync<CreateRobotDialog>("Create Robot", parameters,
|
||||
new DialogOptions { MaxWidth = MaxWidth.Medium, FullWidth = true });
|
||||
var result = await dialog.Result;
|
||||
|
||||
if (result != null && !result.Canceled)
|
||||
{
|
||||
await TableRef?.LoadDataAsync()!;
|
||||
Snackbar.Add("Robot created successfully", Severity.Success);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleDeleted()
|
||||
{
|
||||
await TableRef?.LoadDataAsync()!;
|
||||
Snackbar.Add("Robot deleted successfully", Severity.Success);
|
||||
}
|
||||
|
||||
private async Task HandleUpdated()
|
||||
{
|
||||
await TableRef?.LoadDataAsync()!;
|
||||
Snackbar.Add("Robot updated successfully", Severity.Success);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
@page "/robot-models"
|
||||
|
||||
@rendermode InteractiveWebAssemblyNoPrerender
|
||||
|
||||
<PageTitle>Robot Model Management</PageTitle>
|
||||
|
||||
@using RobotNet10.FleetManager.Client.Services
|
||||
@using RobotNet10.FleetManager.Client.Components.RobotModelManager
|
||||
@using RobotNet10.FleetManager.Client.Components.RobotModelManager.Dialogs
|
||||
@using RobotNet10.FleetManager.Shared.DTOs.RobotModel
|
||||
@inject RobotModelApiService ApiService
|
||||
@inject IDialogService DialogService
|
||||
@inject ISnackbar Snackbar
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraExtraLarge" Class="mt-4">
|
||||
<!-- Header -->
|
||||
<MudPaper Class="pa-4 mb-4 align-content-center" MinHeight="80px">
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween" Spacing="3">
|
||||
<MudText Typo="Typo.h5">Robot Model Management</MudText>
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
||||
<MudTextField Value="@searchText"
|
||||
Placeholder="Search robot models..."
|
||||
Variant="Variant.Outlined"
|
||||
Adornment="Adornment.Start"
|
||||
AdornmentIcon="@Icons.Material.Filled.Search"
|
||||
Margin="Margin.Dense"
|
||||
Style="min-width: 250px;"
|
||||
Immediate="false"
|
||||
T="string"
|
||||
ValueChanged="TextSearchChanged"
|
||||
Clearable="true" />
|
||||
<MudButton StartIcon="@Icons.Material.Filled.Add"
|
||||
Variant="Variant.Filled"
|
||||
Color="Color.Success"
|
||||
OnClick="HandleCreate">
|
||||
Add Robot Model
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
|
||||
<!-- Main Content -->
|
||||
<MudGrid>
|
||||
<!-- Left: List Panel (60%) -->
|
||||
<MudItem xs="12" md="7">
|
||||
<RobotModelListPanel @ref="ListPanelRef"
|
||||
ApiService="@ApiService"
|
||||
SelectedRobotModelChanged="SelectedRobotModelChanged"
|
||||
SearchText="@searchText" />
|
||||
</MudItem>
|
||||
|
||||
<!-- Right: Details Panel (40%) -->
|
||||
<MudItem xs="12" md="5">
|
||||
<MudPaper Class="pa-3" Elevation="1" Style="height: calc(100vh - 169px); overflow-y: auto;">
|
||||
|
||||
@if (selectedRobotModel != null)
|
||||
{
|
||||
<RobotModelDetailsPanel RobotModel="@selectedRobotModel" />
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudText Typo="Typo.body1" Align="Align.Center" Class="mt-8">
|
||||
Select a robot model to view details
|
||||
</MudText>
|
||||
}
|
||||
</MudPaper>
|
||||
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudContainer>
|
||||
|
||||
<MudThemeProvider IsDarkMode />
|
||||
<MudPopoverProvider />
|
||||
<MudDialogProvider />
|
||||
<MudSnackbarProvider />
|
||||
|
||||
@code {
|
||||
private string searchText = string.Empty;
|
||||
private RobotModelDto? selectedRobotModel;
|
||||
private RobotModelListPanel? ListPanelRef;
|
||||
|
||||
private async Task TextSearchChanged(string text)
|
||||
{
|
||||
searchText = text;
|
||||
if (ListPanelRef != null)
|
||||
{
|
||||
await ListPanelRef.SearchAsync(text);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleCreate()
|
||||
{
|
||||
var parameters = new DialogParameters
|
||||
{
|
||||
["ApiService"] = ApiService
|
||||
};
|
||||
|
||||
var dialog = await DialogService.ShowAsync<CreateRobotModelDialog>("Create Robot Model", parameters,
|
||||
new DialogOptions { MaxWidth = MaxWidth.Medium, FullWidth = true });
|
||||
var result = await dialog.Result;
|
||||
|
||||
if (result != null && !result.Canceled)
|
||||
{
|
||||
await ListPanelRef?.LoadDataAsync()!;
|
||||
Snackbar.Add("Robot model created successfully", Severity.Success);
|
||||
}
|
||||
}
|
||||
|
||||
private void SelectedRobotModelChanged(RobotModelDto? model)
|
||||
{
|
||||
selectedRobotModel = model;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
@page "/robots/monitor"
|
||||
@rendermode RobotNet10.Components.RenderMode.InteractiveWebAssemblyNoPrerender
|
||||
@using RobotNet10.FleetManager.Client.Components.RobotMonitor
|
||||
|
||||
<PageTitle>Robot Monitor</PageTitle>
|
||||
|
||||
<RobotMonitorComponent />
|
||||
|
||||
<MudThemeProvider IsDarkMode />
|
||||
<MudPopoverProvider />
|
||||
<MudDialogProvider />
|
||||
<MudSnackbarProvider />
|
||||
@@ -0,0 +1,12 @@
|
||||
@page "/programming"
|
||||
@rendermode InteractiveWebAssemblyNoPrerender
|
||||
|
||||
<PageTitle>Script Editor</PageTitle>
|
||||
|
||||
<RobotNet10.ScriptEditor.ScriptEditor />
|
||||
|
||||
<MudThemeProvider IsDarkMode />
|
||||
<MudPopoverProvider />
|
||||
<MudDialogProvider />
|
||||
<MudSnackbarProvider />
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
@page "/station-manager/{LevelId:guid}"
|
||||
@rendermode RobotNet10.Components.RenderMode.InteractiveWebAssemblyNoPrerender
|
||||
|
||||
<PageTitle>Station Manager</PageTitle>
|
||||
|
||||
<RobotNet10.MapEditor.Components.StationManager.StationManagerComponent LayoutLevelId="LevelId" />
|
||||
|
||||
<MudThemeProvider IsDarkMode />
|
||||
<MudPopoverProvider />
|
||||
<MudDialogProvider />
|
||||
<MudSnackbarProvider />
|
||||
|
||||
@code
|
||||
{
|
||||
[Parameter]
|
||||
public Guid LevelId { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
@page "/vehicletypes/edit/{Id:guid}"
|
||||
@rendermode RobotNet10.Components.RenderMode.InteractiveWebAssemblyNoPrerender
|
||||
|
||||
<PageTitle>Vehicle Editor</PageTitle>
|
||||
|
||||
<RobotNet10.MapEditor.Components.VehicleTypeManager.VehicleTypeEditComponent Id="@Id" />
|
||||
|
||||
<MudThemeProvider IsDarkMode />
|
||||
<MudPopoverProvider />
|
||||
<MudDialogProvider />
|
||||
<MudSnackbarProvider />
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public Guid Id { get; set; }
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
@page "/vehicle-manager"
|
||||
@rendermode RobotNet10.Components.RenderMode.InteractiveWebAssemblyNoPrerender
|
||||
|
||||
<PageTitle>Vehicle Manager</PageTitle>
|
||||
|
||||
<RobotNet10.MapEditor.Components.VehicleTypeManager.VehicleTypeManagerComponent />
|
||||
|
||||
<MudThemeProvider IsDarkMode />
|
||||
<MudPopoverProvider />
|
||||
<MudDialogProvider />
|
||||
<MudSnackbarProvider />
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
@using Microsoft.AspNetCore.Authorization
|
||||
|
||||
@attribute [Authorize]
|
||||
Reference in New Issue
Block a user