Compare commits

...

8 Commits

Author SHA1 Message Date
Đăng Nguyễn
b006c5b197 update console 2025-12-22 19:43:04 +07:00
Đăng Nguyễn
4ceec9abd5 update 2025-12-22 19:31:55 +07:00
Đăng Nguyễn
1289a6c331 update 2025-12-22 18:39:38 +07:00
Đăng Nguyễn
f1a7be15f2 update 2025-12-22 18:38:35 +07:00
d2cf86f34e update 2025-12-22 18:38:10 +07:00
Đăng Nguyễn
d4af3b8707 Merge remote-tracking branch 'origin/sonlt' into dangnv 2025-12-22 18:12:25 +07:00
909e147be1 save 2025-12-22 18:08:02 +07:00
dc837e5488 save 2025-12-22 17:48:48 +07:00
27 changed files with 261 additions and 76 deletions

66
.dockerignore Normal file
View File

@ -0,0 +1,66 @@
# Build artifacts
**/bin/
**/obj/
**/out/
# Visual Studio files
**/.vs/
**/.vscode/
**/*.user
**/*.suo
**/*.userosscache
**/*.sln.docstates
# User-specific files
**/.user
**/.suo
**/.userosscache
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# NuGet packages
**/packages/
**/*.nupkg
**/*.snupkg
# Test results
**/[Tt]est[Rr]esult*/
**/[Bb]uild[Ll]og.*
# Docker files
Dockerfile*
docker-compose*
.dockerignore
# Git
.git/
.gitignore
.gitattributes
# IDE
.idea/
*.swp
*.swo
*~
# OS
.DS_Store
Thumbs.db
# Data and logs (will be mounted as volumes)
data/
logs/

57
Dockerfile Normal file
View File

@ -0,0 +1,57 @@
# Stage 1: Build
# Note: Project files specify net10.0, but using .NET 9.0 based on package versions (9.0.9)
# Adjust version if needed: 8.0 (LTS), 9.0 (current), or future 10.0
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base
WORKDIR /app
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
# Copy solution file
COPY RobotApp.sln .
# Copy project files
COPY RobotApp/RobotApp.csproj RobotApp/
COPY RobotApp.Client/RobotApp.Client.csproj RobotApp.Client/
COPY RobotApp.Common.Shares/RobotApp.Common.Shares.csproj RobotApp.Common.Shares/
COPY RobotApp.VDA5050/RobotApp.VDA5050.csproj RobotApp.VDA5050/
# Restore dependencies
RUN dotnet restore RobotApp.sln
# Copy all source files
COPY RobotApp/ RobotApp/
COPY RobotApp.Client/ RobotApp.Client/
COPY RobotApp.Common.Shares/ RobotApp.Common.Shares/
COPY RobotApp.VDA5050/ RobotApp.VDA5050/
RUN rm -rf ./RobotApp/RobotApp/bin
RUN rm -rf ./RobotApp/RobotApp/obj
RUN rm -rf ./RobotApp.Client/RobotApp.Client/bin
RUN rm -rf ./RobotApp.Client/RobotApp.Client/obj
RUN rm -rf ./RobotApp.Common.Shares/RobotApp.Common.Shares/bin
RUN rm -rf ./RobotApp.Common.Shares/RobotApp.Common.Shares/obj
RUN rm -rf ./RobotApp.VDA5050/RobotApp.VDA5050/bin
RUN rm -rf ./RobotApp.VDA5050/RobotApp.VDA5050/obj
# Build the solution
WORKDIR /src/RobotApp
RUN dotnet build -c Release -o /app/build
# Stage 2: Publish
FROM build AS publish
WORKDIR /src/RobotApp
RUN dotnet publish -c Release -o /app/publish /p:UseAppHost=false
# Copy published files
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish ./
# Set environment variables
#ENV ASPNETCORE_URLS=http://+:8080
ENV ASPNETCORE_ENVIRONMENT=Production
# Run the application
ENTRYPOINT ["dotnet", "RobotApp.dll"]

View File

@ -5,7 +5,6 @@
@using MudBlazor
@implements IDisposable
@attribute [Authorize]
@inject RobotStateClient RobotStateClient
@rendermode InteractiveWebAssemblyNoPrerender

View File

@ -1,6 +1,5 @@
@page "/logs"
@rendermode InteractiveWebAssemblyNoPrerender
@attribute [Authorize]
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@using RobotApp.Client.Models

View File

@ -2,8 +2,6 @@
@rendermode InteractiveWebAssemblyNoPrerender
@attribute [Authorize]
<PageTitle>Map Manager</PageTitle>
<div class="d-flex w-100 h-100 p-2 overflow-hidden flex-row">

View File

@ -89,7 +89,7 @@
</MudSelect>
</MudItem>
<!-- Radius -->
@* <!-- Radius -->
<MudItem xs="6">
<MudNumericField T="double"
Value="@edge.Radius"
@ -127,7 +127,7 @@
Apply Curve (generate node)
</MudButton>
</MudItem>
}
} *@
</MudGrid>
</ChildContent>

View File

@ -10,12 +10,6 @@
<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>

View File

@ -14,6 +14,15 @@
Import JSON
</MudButton>
<!-- CANCEL -->
<MudButton Variant="Variant.Filled"
Color="Color.Error"
StartIcon="@Icons.Material.Filled.Cancel"
Disabled="@DisableCancel"
OnClick="OnCancel">
Cancel
</MudButton>
<!-- SEND -->
<MudButton Variant="Variant.Filled"
Color="@SendButtonColor"
@ -37,8 +46,10 @@
<div class="flex-grow-1">
<MudTextField Value="@OrderJson"
ReadOnly
T="string"
ValueChanged="OrderJsonChange"
Variant="Variant.Filled"
Immediate=true
Lines="50"
Style="font-family: 'Roboto Mono', Consolas, monospace;
font-size: 0.85rem;
@ -51,10 +62,14 @@
[Parameter] public string OrderJson { get; set; } = "";
[Parameter] public bool Copied { get; set; }
[Parameter] public bool? SendSuccess { get; set; }
[Parameter] public bool DisableCancel { get; set; }
[Parameter] public EventCallback<string> OrderJsonChanged { get; set; }
[Parameter] public EventCallback OnCopy { get; set; }
[Parameter] public EventCallback OnSend { get; set; }
[Parameter] public EventCallback OnImport { get; set; }
[Parameter] public EventCallback OnCancel { get; set; }
private string SendButtonText =>
SendSuccess switch
@ -79,4 +94,11 @@
false => Icons.Material.Filled.Error,
_ => Icons.Material.Filled.Send
};
private void OrderJsonChange(string value)
{
OrderJson = value;
OrderJsonChanged.InvokeAsync(OrderJson);
StateHasChanged();
}
}

View File

@ -1,6 +1,5 @@
@page "/robot-order"
@attribute [Authorize]
@rendermode InteractiveWebAssemblyNoPrerender
@using System.Text.Json
@ -8,6 +7,7 @@
@inject IJSRuntime JS
@inject IDialogService DialogService
@inject ISnackbar Snackbar
@inject HttpClient Http
<MudMainContent Class="pa-0 ma-0">
@ -46,13 +46,13 @@
<!-- ================= RIGHT ================= -->
<MudItem xs="12" md="5" Class="h-100">
<JsonOutputPanel OrderJson="@OrderJson"
<JsonOutputPanel @bind-OrderJson="@OrderJson"
Copied="@copied"
SendSuccess="@sendSuccess"
OnCopy="CopyJsonToClipboard"
OnSend="SendOrderToServer"
OnImport="OpenImportDialog" />
OnImport="OpenImportDialog"
OnCancel="CancelOrder" />
</MudItem>
</MudGrid>
@ -66,7 +66,6 @@
private string OrderJson = ""; // 🔥 CACHE JSON (QUAN TRỌNG)
private bool copied;
private bool? sendSuccess;
private bool sending;
private CancellationTokenSource? _copyCts;
// ================= INIT =================
@ -227,7 +226,7 @@
sendSuccess = response.IsSuccessStatusCode;
}
catch (Exception ex)
catch
{
sendSuccess = false;
}
@ -246,6 +245,35 @@
});
}
async Task CancelOrder()
{
try
{
var res = await Http.PostAsync("/api/order/cancel", null);
sendSuccess = null; // reset trạng thái SEND
copied = false;
if (res.IsSuccessStatusCode)
{
Snackbar.Add(
"⛔ Order đã huỷ",
Severity.Info);
}
else
{
Snackbar.Add(
"❌ Huỷ order thất bại",
Severity.Error);
}
}
catch (Exception ex)
{
Snackbar.Add(
$"❌ Lỗi: {ex.Message}",
Severity.Error);
}
}
async Task CopyJsonToClipboard()

View File

@ -1,6 +1,5 @@
@page "/robot-config"
@rendermode InteractiveWebAssemblyNoPrerender
@attribute [Authorize]
@inject HttpClient Http
@inject ISnackbar Snackbar

View File

@ -1,6 +1,5 @@
@page "/robot-monitor"
@rendermode InteractiveWebAssemblyNoPrerender
@attribute [Authorize]
@inject RobotApp.Client.Services.RobotMonitorService MonitorService
@implements IAsyncDisposable
@ -40,3 +39,5 @@
}

View File

@ -59,3 +59,4 @@ window.robotMonitor = {
};

View File

@ -3,8 +3,6 @@
@rendermode InteractiveServer
@attribute [Authorize]
@inject NavigationManager Nav
@code

View File

@ -6,7 +6,8 @@ namespace RobotApp.Controllers;
[Route("api/[controller]")]
[ApiController]
[Authorize]
//[Authorize]
[AllowAnonymous]
public class FileController(Services.Logger<FileController> Logger) : ControllerBase
{
private readonly string certificatesPath = "MqttCertificates";

View File

@ -5,6 +5,7 @@ namespace RobotApp.Controllers;
[Route("api/[controller]")]
[ApiController]
//[Authorize]
[AllowAnonymous]
public class ImagesController(Services.Logger<ImagesController> Logger) : ControllerBase
{

View File

@ -5,7 +5,8 @@ namespace RobotApp.Controllers;
[Route("api/[controller]")]
[ApiController]
[Authorize]
//[Authorize]
[AllowAnonymous]
public class LogsManagerController(Services.Logger<LogsManagerController> Logger) : ControllerBase
{
private readonly string LoggerDirectory = "logs";

View File

@ -1,5 +1,6 @@
using Microsoft.AspNetCore.Mvc;
using RobotApp.Services.Robot;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using RobotApp.Interfaces;
using RobotApp.VDA5050.Order;
using System.Text.Json;
@ -7,30 +8,31 @@ namespace RobotApp.Controllers;
[ApiController]
[Route("api/order")]
public class OrderController : ControllerBase
//[Authorize]
[AllowAnonymous]
public class OrderController(IOrder robotOrderController, IInstantActions instantActions) : 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"
});
}
[HttpPost("cancel")]
public IActionResult CancelOrder()
{
robotOrderController.StopOrder();
instantActions.StopOrderAction();
return Ok(new
{
success = true,
message = "Order and actions have been cancelled"
});
}
}

View File

@ -10,7 +10,8 @@ namespace RobotApp.Controllers;
[Route("api/[controller]")]
[ApiController]
[Authorize]
//[Authorize]
[AllowAnonymous]
public class RobotConfigsController(Services.Logger<RobotConfigsController> Logger, ApplicationDbContext AppDb, RobotConfiguration RobotConfiguration) : ControllerBase
{
[HttpGet]

View File

@ -155,7 +155,7 @@ public static class ApplicationDbExtensions
VDA5050EnableTls = false,
VDA5050UserName = "robotics",
VDA5050Password = "robotics",
VDA5050TopicPrefix = "uagv/v2"
VDA5050TopicPrefix = "uagv/v2",
IsActive = true,
CreatedAt = DateTime.Now,
UpdatedAt = DateTime.Now,

View File

@ -11,3 +11,4 @@ public class RobotMonitorHub : Hub
}

View File

@ -6,22 +6,22 @@
"dotnetRunMessages": true,
"launchBrowser": true,
"workingDirectory": "$(TargetDir)",
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
//"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"applicationUrl": "http://localhost:5229",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"workingDirectory": "$(TargetDir)",
//"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"applicationUrl": "https://0.0.0.0:7150;http://localhost:5229",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
//"https": {
// "commandName": "Project",
// "dotnetRunMessages": true,
// "launchBrowser": true,
// "workingDirectory": "$(TargetDir)",
// //"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
// "applicationUrl": "https://0.0.0.0:7150;http://localhost:5229",
// "environmentVariables": {
// "ASPNETCORE_ENVIRONMENT": "Development"
// }
//}
}
}

View File

@ -195,17 +195,6 @@ public class MQTTClient : IAsyncDisposable
arg.Chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority;
var isValid = arg.Chain.Build((X509Certificate2)arg.Certificate);
if (isValid)
{
Console.WriteLine("Broker CERTIFICATE VALID");
}
else
{
Console.WriteLine("Broker CERTIFICATE INVALID");
foreach (var status in arg.Chain.ChainStatus)
Console.WriteLine($" -> Chain error: {status.Status} - {status.StatusInformation}");
}
return isValid;
}
}

View File

@ -27,7 +27,6 @@ public class MQTTClientCertificatesProvider(string? CerFile, string? KeyFile) :
var cert = X509Certificate2.CreateFromPem(File.ReadAllText(certLocal), File.ReadAllText(keyLocal));
var pfxBytes = cert.Export(X509ContentType.Pfx);
var pfxCert = X509CertificateLoader.LoadPkcs12(pfxBytes, "", X509KeyStorageFlags.Exportable | X509KeyStorageFlags.PersistKeySet);
Console.WriteLine($"Client cert loaded: {pfxCert.Subject}, HasPrivateKey: {pfxCert.HasPrivateKey}, PrivateKey Type: {pfxCert.GetRSAPrivateKey()?.GetType()}");
return [pfxCert];
}
}

View File

@ -96,7 +96,6 @@ public abstract class RobotAction(IServiceProvider serviceProvider) : IDisposabl
protected virtual Task StopAction()
{
Console.WriteLine($"StopAction {Type}");
Status = ActionStatus.FAILED;
ResultDescription = "Action bị hủy bỏ.";
return Task.CompletedTask;

View File

@ -169,15 +169,11 @@ public class RobotStatePublisher : BackgroundService
isConnected, // payload only bool
stoppingToken
);
Console.WriteLine(
$"[RobotStatePublisher] Robot connection changed → {(isConnected ? "ONLINE" : "OFFLINE")}"
);
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
}

View File

@ -158,14 +158,12 @@ public class SimulationNavigation : INavigation, IDisposable
public void Pause()
{
Console.WriteLine($"Nav Pause");
ResumeState = State;
NavState = NavigationState.Paused;
}
public void Resume()
{
Console.WriteLine($"Nav Resume");
NavState = ResumeState;
}

35
docker-compose.yaml Normal file
View File

@ -0,0 +1,35 @@
version: '3.8'
services:
robotapp:
build:
context: .
dockerfile: Dockerfile
container_name: robotapp
restart: unless-stopped
ports:
- "8080:8080"
environment:
- ASPNETCORE_ENVIRONMENT=Production
- ASPNETCORE_URLS=http://+:8080
- ConnectionStrings__DefaultConnection=Data Source=/app/data/robot.db
volumes:
# Persist database
- ./data:/app/data
# Persist maps
- ./maps:/app/maps
# Persist logs (if needed)
- ./logs:/app/logs
networks:
- robotapp-network
healthcheck:
test: ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:8080 || exit 1"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
networks:
robotapp-network:
driver: bridge