save
This commit is contained in:
parent
6cd32f8c98
commit
3e7bcd82b6
|
|
@ -72,6 +72,7 @@
|
|||
new(){Icon = "mdi-view-dashboard", Path="/", Label = "Dashboard", Match = NavLinkMatch.All},
|
||||
// new(){Icon = "mdi-map-legend", Path="/maps-manager", Label = "Mapping", Match = NavLinkMatch.All},
|
||||
new(){Icon = "mdi-application-cog", Path="/robot-config", Label = "Config", Match = NavLinkMatch.All},
|
||||
new(){Icon = "mdi-application-cog", Path="/robot-state", Label = "state", Match = NavLinkMatch.All},
|
||||
];
|
||||
|
||||
private bool collapseNavMenu = true;
|
||||
|
|
|
|||
212
RobotApp.Client/Pages/EditNodeDialog.razor
Normal file
212
RobotApp.Client/Pages/EditNodeDialog.razor
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
@inherits MudComponentBase
|
||||
@using RobotApp.VDA5050.InstantAction
|
||||
@using System.Text.Json
|
||||
@using System.Text.Json.Serialization
|
||||
@using RobotApp.VDA5050.Order
|
||||
@using RobotApp.VDA5050.Type
|
||||
|
||||
<MudDialog>
|
||||
<TitleContent>
|
||||
<MudText Typo="Typo.h6">Edit Node: @Node.NodeId</MudText>
|
||||
</TitleContent>
|
||||
|
||||
<DialogContent>
|
||||
<MudGrid Spacing="3">
|
||||
<MudItem xs="12">
|
||||
<MudTextField @bind-Value="Node.NodeId" Label="Node ID" Required="true" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudNumericField T="int" @bind-Value="Node.SequenceId" Label="Sequence ID" Required="true" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudSwitch T="bool" @bind-Checked="Node.Released" Label="Released" />
|
||||
</MudItem>
|
||||
<MudItem xs="6">
|
||||
<MudNumericField T="double" @bind-Value="Node.NodePosition.X" Label="X" />
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="6">
|
||||
<MudNumericField T="double" @bind-Value="Node.NodePosition.Y" Label="Y" />
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="6">
|
||||
<MudNumericField T="double" @bind-Value="Node.NodePosition.Theta" Label="Theta (rad)" />
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="6">
|
||||
<MudNumericField T="double" @bind-Value="Node.NodePosition.AllowedDeviationXY"
|
||||
Label="Allowed Dev XY" />
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="6">
|
||||
<MudNumericField T="double" @bind-Value="Node.NodePosition.AllowedDeviationTheta"
|
||||
Label="Allowed Dev Theta" />
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12">
|
||||
<MudTextField @bind-Value="Node.NodePosition.MapId" Label="Map ID" />
|
||||
</MudItem>
|
||||
|
||||
|
||||
<MudItem xs="12">
|
||||
<MudDivider Class="my-4" />
|
||||
<MudText Typo="Typo.subtitle1" Class="mb-3">Actions</MudText>
|
||||
|
||||
@foreach (var act in Node.Actions)
|
||||
{
|
||||
<MudPaper Class="pa-3 mb-3" Outlined="true">
|
||||
<MudGrid Spacing="3">
|
||||
<MudItem xs="10">
|
||||
<MudItem xs="12">
|
||||
<MudSelect T="string"
|
||||
Label="Action Type"
|
||||
Dense="true"
|
||||
Required="true"
|
||||
@bind-Value="act.ActionType">
|
||||
|
||||
@foreach (var at in Enum.GetValues<ActionType>())
|
||||
{
|
||||
<MudSelectItem Value="@at.ToString()">
|
||||
@at
|
||||
</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudSelect T="string" @bind-Value="act.BlockingType" Label="Blocking Type">
|
||||
<MudSelectItem Value="@("NONE")">NONE</MudSelectItem>
|
||||
<MudSelectItem Value="@("SOFT")">SOFT</MudSelectItem>
|
||||
<MudSelectItem Value="@("HARD")">HARD</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudTextField @bind-Value="act.ActionId" Label="Action ID" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
|
||||
<MudText Typo="Typo.caption" Class="mt-3 mb-2">Action Parameters</MudText>
|
||||
|
||||
@{
|
||||
var parameters = act.ActionParameters ?? Array.Empty<ActionParameter>();
|
||||
}
|
||||
@foreach (var p in parameters.Cast<UiActionParameter>().ToList())
|
||||
{
|
||||
var param = p; // capture cho lambda
|
||||
<MudGrid Class="mt-1">
|
||||
<MudItem xs="5">
|
||||
<MudTextField @bind-Value="param.Key" Label="Key" />
|
||||
</MudItem>
|
||||
<MudItem xs="5">
|
||||
<MudTextField @bind-Value="param.ValueString" Label="Value" />
|
||||
</MudItem>
|
||||
<MudItem xs="2">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Delete"
|
||||
Color="Color.Error"
|
||||
Size="Size.Small"
|
||||
OnClick="@(() => RemoveParameter(act, param))" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
}
|
||||
|
||||
<MudButton Size="Size.Small"
|
||||
StartIcon="@Icons.Material.Filled.Add"
|
||||
Class="mt-3"
|
||||
OnClick="@(() => AddParameter(act))">
|
||||
Add Parameter
|
||||
</MudButton>
|
||||
|
||||
<MudDivider Class="my-3" />
|
||||
|
||||
<MudButton Size="Size.Small"
|
||||
Color="Color.Error"
|
||||
Variant="Variant.Text"
|
||||
StartIcon="@Icons.Material.Filled.Delete"
|
||||
OnClick="@(() => RemoveAction(act))">
|
||||
Remove Action
|
||||
</MudButton>
|
||||
</MudPaper>
|
||||
}
|
||||
|
||||
<MudButton Size="Size.Small"
|
||||
StartIcon="@Icons.Material.Filled.Add"
|
||||
OnClick="AddNewAction">
|
||||
Add Action
|
||||
</MudButton>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</DialogContent>
|
||||
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Cancel">Cancel</MudButton>
|
||||
<MudButton Color="Color.Primary" Variant="Variant.Filled" OnClick="Submit">Save</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
|
||||
@code {
|
||||
[CascadingParameter] public IMudDialogInstance MudDialog { get; set; } = default!;
|
||||
[Parameter] public Node Node { get; set; } = default!;
|
||||
|
||||
private void Cancel() => MudDialog.Cancel();
|
||||
private void Submit() => MudDialog.Close(DialogResult.Ok(true));
|
||||
|
||||
private void RemoveAction(VDA5050.InstantAction.Action actToRemove)
|
||||
{
|
||||
Node.Actions = Node.Actions
|
||||
.Where(a => a != actToRemove)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
|
||||
private void AddNewAction()
|
||||
{
|
||||
Node.Actions = Node.Actions
|
||||
.Append(new VDA5050.InstantAction.Action
|
||||
{
|
||||
ActionId = Guid.NewGuid().ToString(),
|
||||
ActionType = ActionType.startPause.ToString(),
|
||||
BlockingType = "NONE",
|
||||
ActionParameters = Array.Empty<ActionParameter>()
|
||||
})
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void AddParameter(VDA5050.InstantAction.Action act)
|
||||
{
|
||||
var newParam = new UiActionParameter();
|
||||
|
||||
if (act.ActionParameters == null || act.ActionParameters.Length == 0)
|
||||
{
|
||||
act.ActionParameters = new[] { newParam };
|
||||
}
|
||||
else
|
||||
{
|
||||
var list = act.ActionParameters.ToList();
|
||||
list.Add(newParam);
|
||||
act.ActionParameters = list.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveParameter(VDA5050.InstantAction.Action act, UiActionParameter paramToRemove)
|
||||
{
|
||||
if (act.ActionParameters == null || act.ActionParameters.Length == 0)
|
||||
return;
|
||||
|
||||
act.ActionParameters = act.ActionParameters
|
||||
.Where(p => p != paramToRemove) // so sánh reference
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
// UiActionParameter vẫn giữ như cũ trong trang chính
|
||||
public class UiActionParameter : ActionParameter
|
||||
{
|
||||
[JsonIgnore]
|
||||
public string ValueString
|
||||
{
|
||||
get => Value?.ToString() ?? "";
|
||||
set => Value = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
652
RobotApp.Client/Pages/Home.razor
Normal file
652
RobotApp.Client/Pages/Home.razor
Normal file
|
|
@ -0,0 +1,652 @@
|
|||
@page "/"
|
||||
@using System.Text.Json
|
||||
@using System.Text.Json.Serialization
|
||||
@using RobotApp.VDA5050.InstantAction
|
||||
@using RobotApp.VDA5050.Order
|
||||
@using RobotApp.VDA5050.Type
|
||||
@using System.ComponentModel.DataAnnotations
|
||||
@attribute [Authorize]
|
||||
@rendermode InteractiveWebAssemblyNoPrerender
|
||||
@inject IJSRuntime JS
|
||||
|
||||
@inject IDialogService DialogService
|
||||
|
||||
<MudMainContent Class="pa-0 ma-0">
|
||||
<div style="height:100vh; overflow:hidden;">
|
||||
<MudContainer MaxWidth="MaxWidth.False" Class="pa-4" Style="max-width: 100%; height:100%; display:flex; flex-direction:column;">
|
||||
|
||||
<!-- TIÊU ĐỀ -->
|
||||
<MudText Typo="Typo.h4" Align="Align.Center" Class="mb-6 flex-shrink-0">
|
||||
🧾 VDA5050 Order Editor
|
||||
</MudText>
|
||||
|
||||
<MudGrid Spacing="4" Class="flex-grow-1" Style="overflow:hidden;">
|
||||
|
||||
<!-- ================= CỘT TRÁI (50%) ================= -->
|
||||
<MudItem xs="12" md="7" Class="d-flex flex-column h-100" Style="gap:16px;">
|
||||
<!-- Nodes và Edges chia 1x2, mỗi phần có scroll nội bộ -->
|
||||
<MudGrid Spacing="4" Class="flex-grow-1" Style="overflow:hidden;">
|
||||
<!-- Nodes (trái) -->
|
||||
<MudItem xs="12" md="6" Class="h-100">
|
||||
<MudPaper Class="pa-4 h-100 d-flex flex-column" Elevation="2">
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween" Class="mb-4 flex-shrink-0">
|
||||
<MudText Typo="Typo.h6">📍 Nodes</MudText>
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" StartIcon="@Icons.Material.Filled.Add" OnClick="AddNode">
|
||||
Add Node
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
|
||||
<!-- Cuộn nội bộ chỉ cho phần danh sách Nodes -->
|
||||
<div class="flex-grow-1" style="overflow:auto;">
|
||||
<MudExpansionPanels MultiExpansion="true">
|
||||
@foreach (var node in Order.Nodes)
|
||||
{
|
||||
<MudExpansionPanel @key="node">
|
||||
|
||||
<!-- ===== HEADER ===== -->
|
||||
<TitleContent>
|
||||
<div class="d-flex align-center justify-space-between w-100">
|
||||
<MudText Typo="Typo.subtitle1" Class="fw-bold">
|
||||
@node.NodeId
|
||||
</MudText>
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Edit"
|
||||
Color="Color.Primary"
|
||||
Size="Size.Small"
|
||||
OnClick="@(() => OpenEditNodeDialog(node))"
|
||||
StopPropagation="true" />
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Delete"
|
||||
Color="Color.Error"
|
||||
Size="Size.Small"
|
||||
OnClick="@(() => RemoveNode(node))"
|
||||
StopPropagation="true" />
|
||||
</div>
|
||||
</TitleContent>
|
||||
|
||||
<!-- ===== BODY (BẮT BUỘC ChildContent) ===== -->
|
||||
<ChildContent>
|
||||
<MudGrid Spacing="3">
|
||||
<MudItem xs="12">
|
||||
<MudTextField @bind-Value="node.NodeId" Label="Node ID" />
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12">
|
||||
<MudNumericField T="int"
|
||||
@bind-Value="node.SequenceId"
|
||||
Label="Sequence ID" />
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12">
|
||||
<MudSwitch T="bool"
|
||||
@bind-Checked="node.Released"
|
||||
Label="Released" />
|
||||
</MudItem>
|
||||
|
||||
|
||||
<MudItem xs="6">
|
||||
<MudNumericField T="double"
|
||||
@bind-Value="node.NodePosition.X"
|
||||
Label="X" />
|
||||
</MudItem>
|
||||
<MudItem xs="6">
|
||||
<MudNumericField T="double"
|
||||
@bind-Value="node.NodePosition.Y"
|
||||
Label="Y" />
|
||||
</MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudTextField @bind-Value="node.NodePosition.MapId"
|
||||
Label="Map ID" />
|
||||
</MudItem>
|
||||
<MudItem xs="6">
|
||||
<MudNumericField T="double"
|
||||
@bind-Value="node.NodePosition.Theta"
|
||||
Label="Theta (rad)" />
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="6">
|
||||
<MudNumericField T="double"
|
||||
@bind-Value="node.NodePosition.AllowedDeviationXY"
|
||||
Label="Allowed Dev XY" />
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="6">
|
||||
<MudNumericField T="double"
|
||||
@bind-Value="node.NodePosition.AllowedDeviationTheta"
|
||||
Label="Allowed Dev Theta" />
|
||||
</MudItem>
|
||||
|
||||
|
||||
<MudItem xs="12">
|
||||
<MudDivider Class="my-4" />
|
||||
<MudText Typo="Typo.subtitle1" Class="mb-3">
|
||||
Actions
|
||||
</MudText>
|
||||
|
||||
@foreach (var act in node.Actions)
|
||||
{
|
||||
<MudPaper Class="pa-3 mb-3" Outlined>
|
||||
<MudGrid Spacing="3">
|
||||
<MudItem xs="12">
|
||||
<MudSelect T="string"
|
||||
Label="Action Type"
|
||||
@bind-Value="act.ActionType"
|
||||
Dense="true"
|
||||
Required="true">
|
||||
@foreach (var at in Enum.GetValues<ActionType>())
|
||||
{
|
||||
<MudSelectItem Value="@at.ToString()">
|
||||
@at
|
||||
</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12">
|
||||
<MudSelect T="string"
|
||||
@bind-Value="act.BlockingType"
|
||||
Label="Blocking Type">
|
||||
<MudSelectItem Value="@("NONE")">NONE</MudSelectItem>
|
||||
<MudSelectItem Value="@("SOFT")">SOFT</MudSelectItem>
|
||||
<MudSelectItem Value="@("HARD")">HARD</MudSelectItem>
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12">
|
||||
<MudTextField @bind-Value="act.ActionId"
|
||||
Label="Action ID" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
|
||||
<MudText Typo="Typo.caption" Class="mt-3 mb-2">
|
||||
Action Parameters
|
||||
</MudText>
|
||||
|
||||
@foreach (var p in act.ActionParameters.Cast<UiActionParameter>())
|
||||
{
|
||||
<MudGrid Class="mt-1">
|
||||
<MudItem xs="6">
|
||||
<MudTextField @bind-Value="p.Key" Label="Key" />
|
||||
</MudItem>
|
||||
<MudItem xs="6">
|
||||
<MudTextField @bind-Value="p.ValueString" Label="Value" />
|
||||
</MudItem>
|
||||
<MudItem xs="2">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Delete"
|
||||
Color="Color.Error"
|
||||
OnClick="@(() => RemoveActionParameter(act, p))" />
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
}
|
||||
|
||||
<MudButton Size="Size.Small"
|
||||
StartIcon="@Icons.Material.Filled.Add"
|
||||
Class="mt-3"
|
||||
OnClick="@(() => AddActionParameter(act))">
|
||||
Add Parameter
|
||||
</MudButton>
|
||||
|
||||
<MudDivider Class="my-3" />
|
||||
|
||||
<MudButton Size="Size.Small"
|
||||
Color="Color.Error"
|
||||
Variant="Variant.Text"
|
||||
StartIcon="@Icons.Material.Filled.Delete"
|
||||
OnClick="@(() => RemoveAction(node, act))">
|
||||
Remove Action
|
||||
</MudButton>
|
||||
</MudPaper>
|
||||
}
|
||||
|
||||
<MudButton Size="Size.Small"
|
||||
StartIcon="@Icons.Material.Filled.Add"
|
||||
OnClick="@(() => AddAction(node))">
|
||||
Add Action
|
||||
</MudButton>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</ChildContent>
|
||||
|
||||
</MudExpansionPanel>
|
||||
}
|
||||
</MudExpansionPanels>
|
||||
</div>
|
||||
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
|
||||
<!-- Edges (phải) -->
|
||||
<MudItem xs="12" md="6" Class="h-100">
|
||||
<MudPaper Class="pa-4 h-100 d-flex flex-column" Elevation="2">
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween" Class="mb-4 flex-shrink-0">
|
||||
<MudText Typo="Typo.h6">🔗 Edges</MudText>
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" StartIcon="@Icons.Material.Filled.Add" OnClick="AddEdge">
|
||||
Add Edge
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
|
||||
<!-- Cuộn nội bộ chỉ cho phần danh sách Edges -->
|
||||
<div class="flex-grow-1" style="overflow:auto;">
|
||||
<MudExpansionPanels MultiExpansion="true">
|
||||
@foreach (var edge in Order.Edges)
|
||||
{
|
||||
<MudExpansionPanel Text="@($"{edge.EdgeId} ({edge.StartNodeId} → {edge.EndNodeId})")" @key="edge">
|
||||
<MudGrid Spacing="3">
|
||||
<MudItem xs="12"><MudTextField @bind-Value="edge.EdgeId" Label="Edge ID" /></MudItem>
|
||||
<MudItem xs="12">
|
||||
<MudSelect T="string"
|
||||
Label="Start Node"
|
||||
Dense="true"
|
||||
Required="true"
|
||||
@bind-Value="edge.StartNodeId">
|
||||
@foreach (var node in Order.Nodes)
|
||||
{
|
||||
<MudSelectItem Value="@node.NodeId">
|
||||
@node.NodeId
|
||||
</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12">
|
||||
<MudSelect T="string"
|
||||
Label="End Node"
|
||||
Dense="true"
|
||||
Required="true"
|
||||
@bind-Value="edge.EndNodeId">
|
||||
@foreach (var node in Order.Nodes)
|
||||
{
|
||||
<MudSelectItem Value="@node.NodeId"
|
||||
Disabled="@(node.NodeId == edge.StartNodeId)">
|
||||
@node.NodeId
|
||||
</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12" Class="mt-4">
|
||||
<MudButton Color="Color.Error" Variant="Variant.Text" StartIcon="@Icons.Material.Filled.Delete"
|
||||
OnClick="@(() => RemoveEdge(edge))">Remove Edge</MudButton>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudExpansionPanel>
|
||||
}
|
||||
</MudExpansionPanels>
|
||||
</div>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudItem>
|
||||
<!-- ================= CỘT PHẢI (50%) - JSON Output ================= -->
|
||||
<MudItem xs="12" md="5" Class="h-100">
|
||||
<MudPaper Class="pa-4 h-100 d-flex flex-column" Elevation="2">
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Justify="Justify.SpaceBetween" Class="mb-4 flex-shrink-0">
|
||||
<MudText Typo="Typo.h6">📄 JSON Output (/order)</MudText>
|
||||
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="Color.Success"
|
||||
StartIcon="@Icons.Material.Filled.Send"
|
||||
OnClick="SendOrderToServer">
|
||||
Send
|
||||
</MudButton>
|
||||
@if (!string.IsNullOrEmpty(sendResult))
|
||||
{
|
||||
<MudAlert Severity="@(sendResult.StartsWith("✅") ? Severity.Success : Severity.Error)"
|
||||
Class="mt-3">
|
||||
@sendResult
|
||||
</MudAlert>
|
||||
}
|
||||
|
||||
<MudTooltip Text="@(copied ? "Copied!" : "Copy to clipboard")">
|
||||
<MudButton Variant="Variant.Filled"
|
||||
Color="@(copied ? Color.Success : Color.Primary)"
|
||||
Size="Size.Small"
|
||||
StartIcon="@(copied ? Icons.Material.Filled.Check : Icons.Material.Filled.ContentCopy)"
|
||||
OnClick="CopyJsonToClipboard">
|
||||
@if (copied)
|
||||
{
|
||||
<MudText Class="ml-2">Copied!</MudText>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudText Class="ml-2">Copy</MudText>
|
||||
}
|
||||
</MudButton>
|
||||
</MudTooltip>
|
||||
</MudStack>
|
||||
|
||||
<!-- Cuộn nội bộ cho JSON -->
|
||||
<div class="flex-grow-1" style="overflow:auto;">
|
||||
<MudTextField Value="@OrderJson"
|
||||
ReadOnly="true"
|
||||
Variant="Variant.Filled"
|
||||
Lines="70"
|
||||
Class="h-100"
|
||||
Style="font-family: 'Roboto Mono', Consolas, 'Courier New', monospace;
|
||||
font-size: 0.875rem;
|
||||
background-color: #1e1e1e;
|
||||
color: #d4d4d4;" />
|
||||
</div>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
|
||||
</MudGrid>
|
||||
</MudContainer>
|
||||
</div>
|
||||
</MudMainContent>
|
||||
|
||||
<script>
|
||||
window.copyText = async function (text) {
|
||||
if (navigator.clipboard && window.isSecureContext) {
|
||||
await navigator.clipboard.writeText(text);
|
||||
return true;
|
||||
}
|
||||
|
||||
const textarea = document.createElement("textarea");
|
||||
textarea.value = text;
|
||||
textarea.style.position = "fixed";
|
||||
textarea.style.opacity = "0";
|
||||
|
||||
document.body.appendChild(textarea);
|
||||
textarea.focus();
|
||||
textarea.select();
|
||||
|
||||
try {
|
||||
document.execCommand("copy");
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.error("Copy failed", err);
|
||||
return false;
|
||||
} finally {
|
||||
document.body.removeChild(textarea);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@code {
|
||||
[Inject] HttpClient Http { get; set; } = default!;
|
||||
|
||||
bool sending;
|
||||
string? sendResult;
|
||||
bool IsEditNodeOpen;
|
||||
Node? EditingNode;
|
||||
OrderMessage Order = new();
|
||||
|
||||
async Task SendOrderToServer()
|
||||
{
|
||||
if (sending)
|
||||
return;
|
||||
|
||||
sending = true;
|
||||
sendResult = null;
|
||||
StateHasChanged();
|
||||
|
||||
try
|
||||
{
|
||||
var response = await Http.PostAsJsonAsync(
|
||||
"/api/order",
|
||||
JsonSerializer.Deserialize<JsonElement>(OrderJson)
|
||||
);
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
sendResult = "✅ Done!";
|
||||
}
|
||||
else
|
||||
{
|
||||
sendResult = $"❌ Failed: {response.StatusCode}";
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
sendResult = $"❌ Error: {ex.Message}";
|
||||
}
|
||||
finally
|
||||
{
|
||||
sending = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void AddNode()
|
||||
{
|
||||
Order.Nodes.Add(new Node
|
||||
{
|
||||
NodeId = $"NODE_{Order.Nodes.Count + 1}",
|
||||
SequenceId = Order.Nodes.Count,
|
||||
Released = true,
|
||||
NodePosition = new NodePosition()
|
||||
});
|
||||
}
|
||||
|
||||
void RemoveNode(Node node)
|
||||
{
|
||||
Order.Nodes.Remove(node);
|
||||
|
||||
Order.Edges.RemoveAll(e =>
|
||||
e.StartNodeId == node.NodeId ||
|
||||
e.EndNodeId == node.NodeId);
|
||||
|
||||
for (int i = 0; i < Order.Nodes.Count; i++)
|
||||
Order.Nodes[i].SequenceId = i;
|
||||
}
|
||||
|
||||
void RemoveAction(Node node, VDA5050.InstantAction.Action act)
|
||||
{
|
||||
node.Actions = node.Actions
|
||||
.Where(a => a != act)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
|
||||
void AddAction(Node node)
|
||||
{
|
||||
node.Actions = node.Actions
|
||||
.Append(new VDA5050.InstantAction.Action
|
||||
{
|
||||
ActionId = Guid.NewGuid().ToString(),
|
||||
ActionType = ActionType.startPause.ToString(),
|
||||
BlockingType = "NONE",
|
||||
ActionParameters = Array.Empty<ActionParameter>()
|
||||
})
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
|
||||
private void RemoveActionParameter(VDA5050.InstantAction.Action act, ActionParameter param)
|
||||
{
|
||||
if (act.ActionParameters == null) return;
|
||||
act.ActionParameters = act.ActionParameters
|
||||
.Where(p => p != param)
|
||||
.ToArray();
|
||||
}
|
||||
void AddActionParameter(VDA5050.InstantAction.Action act)
|
||||
{
|
||||
var newList = (act.ActionParameters ?? Array.Empty<ActionParameter>()).ToList();
|
||||
newList.Add(new UiActionParameter());
|
||||
act.ActionParameters = newList.ToArray();
|
||||
}
|
||||
|
||||
void AddEdge()
|
||||
{
|
||||
if (Order.Nodes.Count < 2) return;
|
||||
|
||||
Order.Edges.Add(new VDA5050.Order.Edge
|
||||
{
|
||||
EdgeId = $"EDGE_{Order.Edges.Count + 1}",
|
||||
StartNodeId = Order.Nodes[^2].NodeId,
|
||||
EndNodeId = Order.Nodes[^1].NodeId
|
||||
});
|
||||
}
|
||||
|
||||
bool copied = false;
|
||||
CancellationTokenSource? _copyCts;
|
||||
|
||||
async Task CopyJsonToClipboard()
|
||||
{
|
||||
_copyCts?.Cancel();
|
||||
_copyCts = new();
|
||||
|
||||
var success = await JS.InvokeAsync<bool>(
|
||||
"copyText",
|
||||
OrderJson
|
||||
);
|
||||
|
||||
if (!success)
|
||||
return;
|
||||
|
||||
copied = true;
|
||||
StateHasChanged();
|
||||
|
||||
try
|
||||
{
|
||||
await Task.Delay(1500, _copyCts.Token);
|
||||
}
|
||||
catch { }
|
||||
|
||||
copied = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
|
||||
void RemoveEdge(VDA5050.Order.Edge edge)
|
||||
{
|
||||
Order.Edges.Remove(edge);
|
||||
}
|
||||
|
||||
string OrderJson =>
|
||||
JsonSerializer.Serialize(
|
||||
Order.ToSchemaObject(),
|
||||
new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
|
||||
});
|
||||
|
||||
|
||||
public class UiActionParameter : ActionParameter
|
||||
{
|
||||
[JsonIgnore]
|
||||
public string ValueString
|
||||
{
|
||||
get => Value?.ToString() ?? "";
|
||||
set => Value = value;
|
||||
}
|
||||
}
|
||||
|
||||
public class OrderMessage
|
||||
{
|
||||
public int HeaderId { get; set; }
|
||||
public string Timestamp { get; set; } = "";
|
||||
public string Version { get; set; } = "v1";
|
||||
public string Manufacturer { get; set; } = "PNKX";
|
||||
public string SerialNumber { get; set; } = "AMR-01";
|
||||
public string OrderId { get; set; } = Guid.NewGuid().ToString();
|
||||
public int OrderUpdateId { get; set; }
|
||||
public string? ZoneSetId { get; set; }
|
||||
public List<Node> Nodes { get; set; } = new();
|
||||
public List<VDA5050.Order.Edge> Edges { get; set; } = new();
|
||||
|
||||
public object ToSchemaObject()
|
||||
{
|
||||
int seq = 0;
|
||||
|
||||
return new
|
||||
{
|
||||
headerId = HeaderId++,
|
||||
|
||||
timestamp = string.IsNullOrWhiteSpace(Timestamp)
|
||||
? DateTime.UtcNow.ToString("O")
|
||||
: Timestamp,
|
||||
|
||||
version = Version,
|
||||
manufacturer = Manufacturer,
|
||||
serialNumber = SerialNumber,
|
||||
orderId = OrderId,
|
||||
orderUpdateId = OrderUpdateId,
|
||||
|
||||
zoneSetId = string.IsNullOrWhiteSpace(ZoneSetId)
|
||||
? null
|
||||
: ZoneSetId,
|
||||
|
||||
// ================= NODES =================
|
||||
nodes = Nodes
|
||||
.Select(n => new
|
||||
{
|
||||
nodeId = n.NodeId,
|
||||
sequenceId = seq++,
|
||||
released = n.Released,
|
||||
|
||||
nodePosition = new
|
||||
{
|
||||
x = n.NodePosition.X,
|
||||
y = n.NodePosition.Y,
|
||||
theta = n.NodePosition.Theta,
|
||||
|
||||
allowedDeviationXY = n.NodePosition.AllowedDeviationXY,
|
||||
allowedDeviationTheta = n.NodePosition.AllowedDeviationTheta,
|
||||
|
||||
mapId = string.IsNullOrWhiteSpace(n.NodePosition.MapId)
|
||||
? "MAP_01"
|
||||
: n.NodePosition.MapId
|
||||
},
|
||||
|
||||
actions = n.Actions
|
||||
.Select(a => new
|
||||
{
|
||||
actionId = a.ActionId,
|
||||
actionType = a.ActionType,
|
||||
blockingType = a.BlockingType,
|
||||
|
||||
actionParameters = a.ActionParameters != null
|
||||
? a.ActionParameters
|
||||
.Select(p => new
|
||||
{
|
||||
key = p.Key,
|
||||
value = p.Value
|
||||
})
|
||||
.ToArray()
|
||||
: Array.Empty<object>()
|
||||
})
|
||||
.ToArray()
|
||||
})
|
||||
.ToArray(), // ✅ QUAN TRỌNG
|
||||
|
||||
// ================= EDGES =================
|
||||
edges = Edges
|
||||
.Select(e => new
|
||||
{
|
||||
edgeId = e.EdgeId,
|
||||
sequenceId = seq++,
|
||||
released = true,
|
||||
|
||||
startNodeId = e.StartNodeId,
|
||||
endNodeId = e.EndNodeId,
|
||||
|
||||
actions = Array.Empty<object>()
|
||||
})
|
||||
.ToArray() // ✅ QUAN TRỌNG
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private async Task OpenEditNodeDialog(Node node)
|
||||
{
|
||||
var parameters = new DialogParameters<EditNodeDialog>
|
||||
{
|
||||
{ x => x.Node, node } // Truyền trực tiếp reference gốc
|
||||
};
|
||||
|
||||
var options = new DialogOptions
|
||||
{
|
||||
CloseButton = true,
|
||||
MaxWidth = MaxWidth.Large,
|
||||
FullWidth = true,
|
||||
CloseOnEscapeKey = true
|
||||
};
|
||||
|
||||
var dialog = await DialogService.ShowAsync<EditNodeDialog>($"Edit Node: {node.NodeId}", parameters, options);
|
||||
await dialog.Result; // Đợi dialog đóng
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
397
RobotApp.Client/Pages/State.razor
Normal file
397
RobotApp.Client/Pages/State.razor
Normal file
|
|
@ -0,0 +1,397 @@
|
|||
@page "/robot-state"
|
||||
@using RobotApp.VDA5050.State
|
||||
@rendermode InteractiveWebAssemblyNoPrerender
|
||||
|
||||
@inject StateMsg RobotState
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.False" Class="pa-4">
|
||||
<!-- ===================================================== -->
|
||||
<!-- HEADER -->
|
||||
<!-- ===================================================== -->
|
||||
<MudPaper Class="pa-6 mb-4 d-flex align-center justify-space-between" Elevation="3">
|
||||
<div>
|
||||
<MudText Typo="Typo.h4">🤖 VDA 5050 Robot Dashboard</MudText>
|
||||
<MudText Typo="Typo.subtitle2" Color="Color.Secondary">
|
||||
@RobotState.Version •
|
||||
@RobotState.Manufacturer
|
||||
@RobotState.SerialNumber
|
||||
</MudText>
|
||||
</div>
|
||||
@* @if (RobotState.Current != null)
|
||||
{
|
||||
<MudChip T="string" Size="Size.Large"
|
||||
Color="@(RobotState.Current ? Color.Success : Color.Error)">
|
||||
@(RobotState.Current.IsOnline ? "ONLINE" : "OFFLINE")
|
||||
</MudChip>
|
||||
} *@
|
||||
</MudPaper>
|
||||
|
||||
@if (RobotState == null)
|
||||
{
|
||||
<MudAlert Severity="Severity.Info" Variant="Variant.Outlined">
|
||||
Waiting for robot state (VDA5050)...
|
||||
<MudProgressLinear Indeterminate Class="mt-3" />
|
||||
</MudAlert>
|
||||
}
|
||||
else
|
||||
{
|
||||
var msg = RobotState;
|
||||
|
||||
<!-- ===================================================== -->
|
||||
<!-- 📨 MESSAGE META (DEBUG / TRACE) -->
|
||||
<!-- ===================================================== -->
|
||||
<MudPaper Class="pa-3 mb-4" Elevation="1">
|
||||
<MudGrid Spacing="2">
|
||||
<MudItem xs="12" md="3">
|
||||
<MudText Typo="Typo.caption">HeaderId</MudText>
|
||||
<MudText>@msg.HeaderId</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="5">
|
||||
<MudText Typo="Typo.caption">Timestamp (UTC)</MudText>
|
||||
<MudText>@msg.Timestamp</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="2">
|
||||
<MudText Typo="Typo.caption">Version</MudText>
|
||||
<MudText>@msg.Version</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="12" md="2">
|
||||
<MudText Typo="Typo.caption">OrderUpdateId</MudText>
|
||||
<MudChip T="string" Color="Color.Primary">
|
||||
@msg.OrderUpdateId
|
||||
</MudChip>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudPaper>
|
||||
|
||||
<!-- ===================================================== -->
|
||||
<!-- MAIN GRID -->
|
||||
<!-- ===================================================== -->
|
||||
<MudGrid Spacing="4">
|
||||
<!-- POSITION + VELOCITY -->
|
||||
<MudItem xs="12" md="6" lg="4">
|
||||
<MudPaper Class="pa-5 h-100" Elevation="2">
|
||||
<MudGrid AlignItems="AlignItems.Center">
|
||||
<MudItem xs="8">
|
||||
<MudText Typo="Typo.h6">📍 Position & Velocity</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="4" Class="d-flex justify-end">
|
||||
<MudChip T="string" Size="Size.Small"
|
||||
Color="@(msg.NewBaseRequest ? Color.Success : Color.Error)">
|
||||
NewBase: @(msg.NewBaseRequest ? "TRUE" : "FALSE")
|
||||
</MudChip>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
<MudDivider Class="my-3" />
|
||||
<MudGrid Spacing="1">
|
||||
<MudItem xs="6">
|
||||
<MudText>X: <b>@msg.AgvPosition.X.ToString("F2")</b> m</MudText>
|
||||
<MudText>Y: <b>@msg.AgvPosition.Y.ToString("F2")</b> m</MudText>
|
||||
<MudText>θ: <b>@msg.AgvPosition.Theta.ToString("F2")</b> rad</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="6">
|
||||
<MudText>Vx: <b>@msg.Velocity.Vx.ToString("F2")</b> m/s</MudText>
|
||||
<MudText>Vy: <b>@msg.Velocity.Vy.ToString("F2")</b> m/s</MudText>
|
||||
<MudText>Ω: <b>@msg.Velocity.Omega.ToString("F3")</b> rad/s</MudText>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
<MudDivider Class="my-3" />
|
||||
<MudGrid Spacing="1">
|
||||
<MudItem xs="6">
|
||||
<MudChip T="string" Size="Size.Small"
|
||||
Color="@(msg.AgvPosition.PositionInitialized ? Color.Success : Color.Error)">
|
||||
Initialized: @(msg.AgvPosition.PositionInitialized ? "TRUE" : "FALSE")
|
||||
</MudChip>
|
||||
</MudItem>
|
||||
<MudItem xs="6" Class="d-flex justify-end">
|
||||
<MudText Typo="Typo.caption">
|
||||
DeviationRange: <b>@msg.AgvPosition.DeviationRange</b>
|
||||
</MudText>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
<MudProgressLinear Value="@(msg.AgvPosition.LocalizationScore * 100)"
|
||||
Class="mt-3"
|
||||
Color="Color.Success" />
|
||||
<MudText Typo="Typo.caption">
|
||||
Localization Score: @(msg.AgvPosition.LocalizationScore * 100) %
|
||||
</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
|
||||
<!-- BATTERY -->
|
||||
<MudItem xs="12" md="6" lg="4">
|
||||
<MudPaper Class="pa-5 h-100" Elevation="2">
|
||||
<MudText Typo="Typo.h6">🔋 Battery</MudText>
|
||||
<MudDivider Class="my-3" />
|
||||
<MudProgressLinear Value="@msg.BatteryState.BatteryCharge"
|
||||
Size="Size.Large"
|
||||
Rounded
|
||||
Color="@(msg.BatteryState.BatteryCharge > 50 ? Color.Success :
|
||||
msg.BatteryState.BatteryCharge > 20 ? Color.Warning : Color.Error)" />
|
||||
<MudText Typo="Typo.h4" Class="mt-2">
|
||||
@msg.BatteryState.BatteryCharge:F1 %
|
||||
</MudText>
|
||||
<MudChip T="string" Size="Size.Small"
|
||||
Color="@(msg.BatteryState.Charging ? Color.Info : Color.Default)">
|
||||
@(msg.BatteryState.Charging ? "⚡ Charging" : "Discharging")
|
||||
</MudChip>
|
||||
<MudDivider Class="my-3" />
|
||||
<MudGrid Spacing="1">
|
||||
<MudItem xs="4">
|
||||
<MudText Typo="Typo.caption">Voltage</MudText>
|
||||
<MudText><b>@msg.BatteryState.BatteryVoltage.ToString("F1")</b> V</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="4">
|
||||
<MudText Typo="Typo.caption">Health</MudText>
|
||||
<MudText><b>@msg.BatteryState.BatteryHealth</b> %</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="4">
|
||||
<MudText Typo="Typo.caption">Reach</MudText>
|
||||
<MudText><b>@((int)msg.BatteryState.Reach)</b> m</MudText>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
|
||||
<!-- ORDER & PATH -->
|
||||
<MudItem xs="12" md="6" lg="4">
|
||||
<MudPaper Class="pa-5 h-100" Elevation="2">
|
||||
<MudText Typo="Typo.h6">🧭 Order & Path</MudText>
|
||||
<MudDivider Class="my-3" />
|
||||
<MudText>Order ID: <b>@(msg.OrderId ?? "—")</b></MudText>
|
||||
<MudText>Update ID: <b>@msg.OrderUpdateId</b></MudText>
|
||||
<MudDivider Class="my-2" />
|
||||
<MudText>
|
||||
Last Node: <b>@msg.LastNodeId</b>
|
||||
<MudText Typo="Typo.caption" Inline="true">Seq: @msg.LastNodeSequenceId</MudText>
|
||||
</MudText>
|
||||
<MudText>Distance: @msg.DistanceSinceLastNode:F1 m</MudText>
|
||||
<MudDivider Class="my-3" />
|
||||
|
||||
@{
|
||||
var nodeReleased = msg.NodeStates?.Count(n => n.Released) ?? 0;
|
||||
var nodeTotal = msg.NodeStates?.Length ?? 0;
|
||||
var edgeReleased = msg.EdgeStates?.Count(e => e.Released) ?? 0;
|
||||
var edgeTotal = msg.EdgeStates?.Length ?? 0;
|
||||
}
|
||||
|
||||
<div class="d-flex align-center flex-wrap gap-2">
|
||||
<MudChip T="string" Color="Color.Info">Nodes: @nodeReleased / @nodeTotal</MudChip>
|
||||
<MudChip T="string" Color="Color.Info">Edges: @edgeReleased / @edgeTotal</MudChip>
|
||||
<MudChip T="string" Size="Size.Small" Color="@(msg.Driving ? Color.Success : Color.Default)">
|
||||
@(msg.Driving ? "DRIVING" : "STOPPED")
|
||||
</MudChip>
|
||||
<MudChip T="string" Size="Size.Small" Color="@(msg.Paused ? Color.Warning : Color.Success)">
|
||||
@(msg.Paused ? "PAUSED" : "ACTIVE")
|
||||
</MudChip>
|
||||
</div>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
|
||||
<!-- ERRORS + INFORMATION -->
|
||||
<MudItem xs="12" md="12" lg="6">
|
||||
<MudPaper Class="pa-5 h-100" Elevation="2">
|
||||
|
||||
<MudText Typo="Typo.h6">🚨 Errors & Information</MudText>
|
||||
<MudDivider Class="my-3" />
|
||||
|
||||
@{
|
||||
var rows = new List<MessageRow>();
|
||||
|
||||
if (msg.Errors != null)
|
||||
{
|
||||
foreach (var err in msg.Errors)
|
||||
{
|
||||
rows.Add(new MessageRow(
|
||||
err.ErrorType ?? "-",
|
||||
err.ErrorLevel ?? "-",
|
||||
err.ErrorDescription ?? "",
|
||||
true
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if (msg.Information != null)
|
||||
{
|
||||
foreach (var info in msg.Information)
|
||||
{
|
||||
rows.Add(new MessageRow(
|
||||
info.InfoType ?? "-",
|
||||
info.InfoLevel ?? "-",
|
||||
info.InfoDescription ?? "",
|
||||
false
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
var sortedMessages = rows
|
||||
.OrderBy(r => r.IsError ? 0 : 1) // Errors trước
|
||||
.ThenBy(r => r.Type)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
<MudTable Items="@sortedMessages"
|
||||
Dense="true"
|
||||
Hover="true"
|
||||
Bordered="true"
|
||||
Elevation="0"
|
||||
Height="180px"
|
||||
Breakpoint="Breakpoint.Sm"
|
||||
HorizontalScrollbar="true">
|
||||
|
||||
<ColGroup>
|
||||
<col style="width: 35%" />
|
||||
<col style="width: 20%" />
|
||||
<col />
|
||||
</ColGroup>
|
||||
|
||||
<HeaderContent>
|
||||
<MudTh>Type</MudTh>
|
||||
<MudTh>Level</MudTh>
|
||||
<MudTh>Description</MudTh>
|
||||
</HeaderContent>
|
||||
|
||||
<RowTemplate>
|
||||
<MudTd DataLabel="Type">
|
||||
<MudText Class="@(context.IsError ? "text-error" : "text-info")">
|
||||
<b>@context.Type</b>
|
||||
</MudText>
|
||||
</MudTd>
|
||||
|
||||
<MudTd DataLabel="Level">
|
||||
<MudChip T="string"
|
||||
Size="Size.Small"
|
||||
Color="@(context.Level == "ERROR"
|
||||
? Color.Error
|
||||
: context.Level == "WARNING"
|
||||
? Color.Warning
|
||||
: Color.Info)">
|
||||
@context.Level
|
||||
</MudChip>
|
||||
</MudTd>
|
||||
|
||||
<MudTd DataLabel="Description">
|
||||
<MudText Typo="Typo.caption"
|
||||
Class="text-truncate"
|
||||
Title="@context.Description">
|
||||
@context.Description
|
||||
</MudText>
|
||||
</MudTd>
|
||||
</RowTemplate>
|
||||
|
||||
<NoRecordsContent>
|
||||
<MudText Typo="Typo.caption"
|
||||
Color="Color.Secondary"
|
||||
Class="pa-4 text-center">
|
||||
No errors or information messages
|
||||
</MudText>
|
||||
</NoRecordsContent>
|
||||
|
||||
</MudTable>
|
||||
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
|
||||
<!-- ACTIONS -->
|
||||
<MudItem xs="12" md="6" lg="3">
|
||||
<MudPaper Class="pa-5 h-100" Elevation="2">
|
||||
|
||||
<MudText Typo="Typo.h6">⚙️ Actions</MudText>
|
||||
<MudDivider Class="my-3" />
|
||||
|
||||
<MudTable Items="msg.ActionStates"
|
||||
Dense="true"
|
||||
Hover="true"
|
||||
Bordered="true"
|
||||
Elevation="0"
|
||||
Height="160px"
|
||||
Breakpoint="Breakpoint.Sm"
|
||||
HorizontalScrollbar="true"
|
||||
FixedHeader="true">
|
||||
|
||||
<ColGroup>
|
||||
<col style="width: 40%" />
|
||||
<col style="width: 35%" />
|
||||
<col style="width: 25%" />
|
||||
</ColGroup>
|
||||
|
||||
<HeaderContent>
|
||||
<MudTh>Action</MudTh>
|
||||
<MudTh>Action ID</MudTh>
|
||||
<MudTh class="text-right">Status</MudTh>
|
||||
</HeaderContent>
|
||||
|
||||
<RowTemplate>
|
||||
<MudTd DataLabel="Action">
|
||||
<MudText Typo="Typo.body2"
|
||||
Class="text-truncate"
|
||||
Title="@context.ActionType">
|
||||
@context.ActionType
|
||||
</MudText>
|
||||
</MudTd>
|
||||
|
||||
<MudTd DataLabel="Action ID">
|
||||
<MudText Typo="Typo.caption">
|
||||
@context.ActionId
|
||||
</MudText>
|
||||
</MudTd>
|
||||
|
||||
<MudTd DataLabel="Status" Class="text-right">
|
||||
<MudChip T="string"
|
||||
Size="Size.Small"
|
||||
Variant="Variant.Filled"
|
||||
Color="@(context.ActionStatus == "RUNNING"
|
||||
? Color.Info
|
||||
: context.ActionStatus == "FINISHED"
|
||||
? Color.Success
|
||||
: Color.Error)">
|
||||
@context.ActionStatus
|
||||
</MudChip>
|
||||
</MudTd>
|
||||
</RowTemplate>
|
||||
|
||||
<NoRecordsContent>
|
||||
<MudText Typo="Typo.caption"
|
||||
Color="Color.Secondary"
|
||||
Class="pa-4 text-center">
|
||||
No active actions
|
||||
</MudText>
|
||||
</NoRecordsContent>
|
||||
|
||||
</MudTable>
|
||||
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
|
||||
<!-- SAFETY -->
|
||||
<MudItem xs="12" md="6" lg="3">
|
||||
<MudPaper Class="pa-5 h-100" Elevation="2">
|
||||
<MudText Typo="Typo.h6">🛑 Safety</MudText>
|
||||
<MudDivider Class="my-3" />
|
||||
<MudChip T="string" Size="Size.Large" Class="w-100 mb-2"
|
||||
Color="@(msg.SafetyState.EStop == "NONE" ? Color.Success : Color.Error)">
|
||||
E-STOP: @msg.SafetyState.EStop
|
||||
</MudChip>
|
||||
<MudChip T="string" Size="Size.Large" Class="w-100"
|
||||
Color="@(msg.SafetyState.FieldViolation ? Color.Error : Color.Success)">
|
||||
Field Violation: @(msg.SafetyState.FieldViolation ? "YES" : "NO")
|
||||
</MudChip>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
|
||||
</MudGrid>
|
||||
}
|
||||
</MudContainer>
|
||||
|
||||
@code {
|
||||
|
||||
private async Task OnStateChanged()
|
||||
=> await InvokeAsync(StateHasChanged);
|
||||
|
||||
|
||||
private record MessageRow(
|
||||
string Type,
|
||||
string Level,
|
||||
string Description,
|
||||
bool IsError
|
||||
);
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<NoDefaultLaunchSettingsFile>true</NoDefaultLaunchSettingsFile>
|
||||
|
|
@ -12,11 +12,13 @@
|
|||
<PackageReference Include="Excubo.Blazor.Canvas" Version="3.2.91" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="9.0.1" />
|
||||
<PackageReference Include="MudBlazor" Version="8.12.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="10.0.1" />
|
||||
<PackageReference Include="MudBlazor" Version="8.15.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\RobotApp.Common.Shares\RobotApp.Common.Shares.csproj" />
|
||||
<ProjectReference Include="..\RobotApp.VDA5050\RobotApp.VDA5050.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.14.36511.14
|
||||
# Visual Studio Version 18
|
||||
VisualStudioVersion = 18.0.11205.157 d18.0
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RobotApp", "RobotApp\RobotApp.csproj", "{BF0BB137-2EF9-4E1B-944E-9BF41C5284F7}"
|
||||
EndProject
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
@page "/"
|
||||
@page "/home"
|
||||
@using Microsoft.AspNetCore.Authorization
|
||||
|
||||
@rendermode InteractiveServer
|
||||
|
|
|
|||
36
RobotApp/Controllers/OrderController.cs
Normal file
36
RobotApp/Controllers/OrderController.cs
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using RobotApp.Services.Robot;
|
||||
using RobotApp.VDA5050.Order;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace RobotApp.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/order")]
|
||||
public class OrderController : ControllerBase
|
||||
{
|
||||
private readonly RobotOrderController robotOrderController;
|
||||
|
||||
public OrderController(RobotOrderController robotOrderController)
|
||||
{
|
||||
this.robotOrderController = robotOrderController;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public IActionResult SendOrder([FromBody] OrderMsg order)
|
||||
{
|
||||
Console.WriteLine("===== ORDER RECEIVED =====");
|
||||
Console.WriteLine(JsonSerializer.Serialize(order, new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true
|
||||
}));
|
||||
|
||||
robotOrderController.UpdateOrder(order);
|
||||
|
||||
return Ok(new
|
||||
{
|
||||
success = true,
|
||||
message = "Order received"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Identity;
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using MudBlazor.Services;
|
||||
using NLog.Web;
|
||||
using RobotApp.Client;
|
||||
using RobotApp.Components;
|
||||
using RobotApp.Components.Account;
|
||||
using RobotApp.Data;
|
||||
|
|
@ -27,7 +28,7 @@ builder.Services.AddAuthorization();
|
|||
|
||||
// Add Controllers for API endpoints
|
||||
builder.Services.AddControllers();
|
||||
|
||||
builder.Services.AddSignalR();
|
||||
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
|
||||
Action<DbContextOptionsBuilder> appDbOptions = options => options.UseSqlite(connectionString, b => b.MigrationsAssembly("RobotApp"));
|
||||
|
||||
|
|
@ -76,6 +77,7 @@ app.UseAntiforgery();
|
|||
|
||||
app.MapStaticAssets();
|
||||
|
||||
|
||||
// Map API Controllers
|
||||
app.MapControllers();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<UserSecretsId>aspnet-RobotApp-1f61caa2-bbbb-40cd-88b6-409b408a84ea</UserSecretsId>
|
||||
|
|
|
|||
|
|
@ -258,7 +258,7 @@ public class RobotOrderController(INavigation NavigationManager,
|
|||
}
|
||||
}
|
||||
|
||||
private void HandleUpdateOrder(OrderMsg order)
|
||||
public void HandleUpdateOrder(OrderMsg order)
|
||||
{
|
||||
if (order.OrderId != OrderId) throw new OrderException(RobotErrors.Error1001(OrderId, order.OrderId));
|
||||
if (order.OrderUpdateId <= OrderUpdateId) return;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user