This commit is contained in:
Đăng Nguyễn 2025-10-31 15:03:37 +07:00
parent aa2146e383
commit aea55d52f1
16 changed files with 1907 additions and 222 deletions

View File

@ -1,63 +1,133 @@
<EditForm Model="@Local" OnValidSubmit="OnSubmit">
@implements IDisposable
<div class="d-flex w-100 h-100 flex-column">
<EditForm EditContext="EditContext">
<DataAnnotationsValidator />
<ValidationSummary />
<div class="row g-2 mb-2">
<div class="col-md-6">
<label class="form-label">Serial Number</label>
<InputText class="form-control" @bind-Value="Local.SerialNumber" />
</div>
<div class="col-md-6">
<label class="form-label">Manufacturer</label>
<InputText class="form-control" @bind-Value="Local.VDA5050Manufacturer" />
</div>
</div>
<div class="row g-2 mb-2">
<div class="col-md-6">
<label class="form-label">Host</label>
<InputText class="form-control" @bind-Value="Local.VDA5050HostServer" />
</div>
<div class="col-md-3">
<label class="form-label">Port</label>
<InputNumber class="form-control" @bind-Value="Local.VDA5050Port" />
</div>
<div class="col-md-3">
<label class="form-label">Publish Repeat</label>
<InputNumber class="form-control" @bind-Value="Local.VDA5050PublishRepeat" />
</div>
</div>
<div class="row g-2 mb-2">
<div class="col-md-6">
<label class="form-label">Username</label>
<InputText class="form-control" @bind-Value="Local.VDA5050UserName" />
</div>
<div class="col-md-6">
<label class="form-label">Password</label>
<InputText class="form-control" @bind-Value="Local.VDA5050Password" type="password" />
</div>
</div>
<div class="mb-2">
<label class="form-label">Version</label>
<InputText class="form-control" @bind-Value="Local.VDA5050Version" />
<label class="form-label" for="serialNumber">Serial Number</label>
<InputText id="serialNumber" class="form-control" @bind-Value="Model.SerialNumber" />
<ValidationMessage For="@(() => Model.SerialNumber)" />
</div>
<div class="row g-2 mb-2">
<div class="col-md-6">
<label class="form-label" for="manufacturer">Manufacturer</label>
<InputText id="manufacturer" class="form-control" @bind-Value="Model.VDA5050Manufacturer" disabled="true" />
<ValidationMessage For="@(() => Model.VDA5050Manufacturer)" />
</div>
<div class="col-md-6">
<label class="form-label" for="version">Version</label>
<InputText id="version" class="form-control" @bind-Value="Model.VDA5050Version" disabled="true" />
<ValidationMessage For="@(() => Model.VDA5050Version)" />
</div>
</div>
<div class="row g-2 mb-2">
<div class="col-md-6">
<label class="form-label" for="host">Host</label>
<InputText id="host" class="form-control" @bind-Value="Model.VDA5050HostServer" />
<ValidationMessage For="@(() => Model.VDA5050HostServer)" />
</div>
<div class="col-md-3">
<label class="form-label" for="port">Port</label>
<InputNumber id="port" class="form-control" @bind-Value="Model.VDA5050Port" />
<ValidationMessage For="@(() => Model.VDA5050Port)" />
</div>
<div class="col-md-3">
<label class="form-label" for="publishRepeat">Publish Repeat</label>
<InputNumber id="publishRepeat" class="form-control" @bind-Value="Model.VDA5050PublishRepeat" />
<ValidationMessage For="@(() => Model.VDA5050PublishRepeat)" />
</div>
</div>
<div class="row g-2 mb-2">
<div class="col-md-6">
<label class="form-label" for="username">Username</label>
<InputText id="username" class="form-control" @bind-Value="Model.VDA5050UserName" />
<ValidationMessage For="@(() => Model.VDA5050UserName)" />
</div>
<div class="col-md-6">
<label class="form-label" for="password">Password</label>
<div class="password-input-wrapper">
<InputText id="password" class="form-control password-input" @bind-Value="Model.VDA5050Password" inputmode="text" type="@PasswordInputType" autocomplete="new-password" spellcheck="false" />
<button class="password-toggle-btn" type="button" @onclick="TogglePasswordVisibility" aria-label="Toggle password visibility">
<i class="@PasswordIconClass" aria-hidden="true"></i>
</button>
</div>
<ValidationMessage For="@(() => Model.VDA5050Password)" />
</div>
</div>
<div class="form-check mb-2">
<InputCheckbox class="form-check-input" @bind-Value="Local.VDA5050EnablePassword" />
<label class="form-check-label">Enable Password</label>
<InputCheckbox id="enablePassword" class="form-check-input" @bind-Value="Model.VDA5050EnablePassword" />
<label class="form-check-label" for="enablePassword">Enable Password</label>
<ValidationMessage For="@(() => Model.VDA5050EnablePassword)" />
</div>
<div class="form-check mb-2">
<InputCheckbox class="form-check-input" @bind-Value="Local.VDA5050EnableTls" />
<label class="form-check-label">Enable TLS</label>
<InputCheckbox id="enableTls" class="form-check-input" @bind-Value="Model.VDA5050EnableTls" />
<label class="form-check-label" for="enableTls">Enable TLS</label>
<ValidationMessage For="@(() => Model.VDA5050EnableTls)" />
</div>
<div class="row g-0 mb-2">
<label class="form-label" for="caFile">CA File</label>
<InputFile class="form-control" id="caFile" OnChange="e => OnFileSelected(e, FileSlot.Ca)" accept=".crt,.pem,.cer,.pfx,.key,.jks" />
@if (!string.IsNullOrEmpty(CaFileName))
{
<div class="small text-muted mt-1 mb-1">@CaFileName (@FormatSize(CaFileSize))</div>
}
@if (!string.IsNullOrEmpty(CaFileError))
{
<div class="text-danger small mt-1 mb-1">@CaFileError</div>
}
</div>
<div class="row g-0 mb-2">
<label class="form-label" for="clientCertFile">Client Certificate File</label>
<InputFile class="form-control" id="clientCertFile" OnChange="e => OnFileSelected(e, FileSlot.Cert)" accept=".crt,.pem,.cer,.pfx,.key,.jks" />
@if (!string.IsNullOrEmpty(CertFileName))
{
<div class="small text-muted mt-1 mb-1">@CertFileName (@FormatSize(CertFileSize))</div>
}
@if (!string.IsNullOrEmpty(CertFileError))
{
<div class="text-danger small mt-1 mb-1">@CertFileError</div>
}
</div>
<div class="row g-0 mb-2">
<label class="form-label" for="clientKeyFile">Client Key File</label>
<InputFile class="form-control" id="clientKeyFile" OnChange="e => OnFileSelected(e, FileSlot.Key)" accept=".crt,.pem,.cer,.pfx,.key,.jks" />
@if (!string.IsNullOrEmpty(KeyFileName))
{
<div class="small text-muted mt-1 mb-1">@KeyFileName (@FormatSize(KeyFileSize))</div>
}
@if (!string.IsNullOrEmpty(KeyFileError))
{
<div class="text-danger small mt-1 mb-1">@KeyFileError</div>
}
</div>
<div class="mb-2">
<label class="form-label">Description</label>
<InputTextArea class="form-control" @bind-Value="Local.Description" />
<label class="form-label" for="description">Description</label>
<InputTextArea id="description m-1" class="form-control" @bind-Value="Model.Description" />
<ValidationMessage For="@(() => Model.Description)" />
</div>
</EditForm>
</EditForm>
<div class="flex-grow-1" />
<div>
@if (Model.CreatedAt != default || Model.UpdatedAt != default)
{
<div class="d-flex justify-content-end mt-2">
<small class="text-muted">Created: @Model.CreatedAt.ToString("dd/MM/yyyy HH:mm:ss")</small>
<small class="text-muted ms-3">Updated: @Model.UpdatedAt.ToString("dd/MM/yyyy HH:mm:ss")</small>
</div>
}
</div>
</div>
@code {
[Parameter]
@ -66,16 +136,136 @@
[Parameter]
public EventCallback<RobotVDA5050ConfigDto> ModelChanged { get; set; }
private RobotVDA5050ConfigDto Local = new();
private EditContext? EditContext;
private bool showPassword;
private string PasswordInputType => showPassword ? "text" : "password";
private string PasswordIconClass => showPassword ? "mdi mdi-eye-off" : "mdi mdi-eye";
private enum FileSlot { Ca, Cert, Key }
private string? CaFileName;
private long CaFileSize;
private string? CaFileError;
private byte[]? CaFileData;
private string? CertFileName;
private long CertFileSize;
private string? CertFileError;
private byte[]? CertFileData;
private string? KeyFileName;
private long KeyFileSize;
private string? KeyFileError;
private byte[]? KeyFileData;
private const long MaxFileSize = 10 * 1024 * 1024; // 10 MB
private async Task OnFileSelected(InputFileChangeEventArgs e, FileSlot slot)
{
var file = e.File;
if (file is null)
return;
if (file.Size > MaxFileSize)
{
SetFileError(slot, $"File too large (max {FormatSize(MaxFileSize)})");
SetFileInfo(slot, null, 0, null);
return;
}
try
{
using var stream = file.OpenReadStream(MaxFileSize);
using var ms = new MemoryStream();
await stream.CopyToAsync(ms);
var data = ms.ToArray();
SetFileData(slot, data, file.Name, file.Size);
ClearFileError(slot);
// Optionally propagate change to parent via ModelChanged if required
// _ = ModelChanged.InvokeAsync(Model);
}
catch (Exception ex)
{
SetFileError(slot, "Failed to read file");
Console.Error.WriteLine(ex);
}
}
private void SetFileData(FileSlot slot, byte[] data, string name, long size)
{
switch (slot)
{
case FileSlot.Ca:
CaFileData = data; CaFileName = name; CaFileSize = size; break;
case FileSlot.Cert:
CertFileData = data; CertFileName = name; CertFileSize = size; break;
case FileSlot.Key:
KeyFileData = data; KeyFileName = name; KeyFileSize = size; break;
}
}
private void SetFileError(FileSlot slot, string? error)
{
switch (slot)
{
case FileSlot.Ca: CaFileError = error; break;
case FileSlot.Cert: CertFileError = error; break;
case FileSlot.Key: KeyFileError = error; break;
}
}
private void ClearFileError(FileSlot slot) => SetFileError(slot, null);
private void SetFileInfo(FileSlot slot, string? name, long size, byte[]? data)
{
switch (slot)
{
case FileSlot.Ca: CaFileName = name; CaFileSize = size; CaFileData = data; break;
case FileSlot.Cert: CertFileName = name; CertFileSize = size; CertFileData = data; break;
case FileSlot.Key: KeyFileName = name; KeyFileSize = size; KeyFileData = data; break;
}
}
private static string FormatSize(long size)
{
if (size <= 0) return "0 B";
if (size < 1024) return $"{size} B";
double kb = size / 1024.0;
if (kb < 1024) return $"{kb:F1} KB";
double mb = kb / 1024.0;
return $"{mb:F2} MB";
}
protected override void OnParametersSet()
{
Local = Model is not null ? Model with { } : new RobotVDA5050ConfigDto();
if (EditContext is null || !EditContext.Model!.Equals(Model))
{
if (EditContext is not null) EditContext.OnFieldChanged -= EditContext_OnFieldChanged;
EditContext = new EditContext(Model);
EditContext.OnFieldChanged += EditContext_OnFieldChanged;
}
CertFileName = string.Empty;
CertFileError = string.Empty;
KeyFileName = string.Empty;
KeyFileError = string.Empty;
CaFileName = string.Empty;
CaFileError = string.Empty;
}
private async Task OnSubmit()
private void TogglePasswordVisibility()
{
Model = Local;
await ModelChanged.InvokeAsync(Model);
showPassword = !showPassword;
}
private void EditContext_OnFieldChanged(object? sender, FieldChangedEventArgs e)
{
_ = ModelChanged.InvokeAsync(Model);
}
public void Dispose()
{
if (EditContext is not null) EditContext.OnFieldChanged -= EditContext_OnFieldChanged;
}
}

View File

@ -0,0 +1,43 @@
/* wrapper positions the toggle inside the input */
.password-input-wrapper {
position: relative;
display: block;
}
/* add right padding so text doesn't overlap the icon */
.password-input {
padding-right: 2.25rem; /* adjust as needed for icon size */
}
/* icon button sits absolutely inside the input at the right */
.password-toggle-btn {
position: absolute;
right: 0.5rem;
top: 50%;
transform: translateY(-50%);
border: none;
background: transparent;
padding: 0;
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
color: inherit;
line-height: 1;
}
/* keep the native focus behavior but provide visible ring for keyboard users */
.password-toggle-btn:focus {
outline: none;
}
.password-toggle-btn:focus-visible {
box-shadow: 0 0 0 3px rgba(13, 110, 253, 0.15);
border-radius: 0.25rem;
}
/* tune icon size */
.password-toggle-btn .mdi {
font-size: 1.15rem;
pointer-events: none; /* let clicks hit the button, not the icon */
}

View File

@ -2,18 +2,20 @@
@rendermode InteractiveWebAssemblyNoPrerender
@attribute [Authorize]
@using RobotApp.Common.Shares.Dtos
@using RobotApp.Common.Shares.Enums
@inject HttpClient Http
@inject ISnackbar Snackbar
@using System.Net.Http.Json
@using RobotApp.Common.Shares.Dtos
<PageTitle>Robot Configuration</PageTitle>
<div class="d-flex w-100 h-100 p-2 overflow-hidden flex-column">
<div class="d-flex w-100 h-100 p-2 overflow-hidden flex-column position-relative">
<div class="rcm-toolbar">
<div class="rcm-toolbar-left">
<label class="rcm-label" for="configType">Config Type</label>
<div class="rcm-select-wrapper">
<select id="configType" class="form-select rcm-select" @bind="SelectedType">
<select id="configType" class="form-select rcm-select" value="@SelectedType" @onchange="OnTypeChanged">
@foreach (var type in Enum.GetValues<RobotConfigType>())
{
<option value="@type">@type</option>
@ -21,7 +23,7 @@
</select>
<span class="rcm-select-icon" aria-hidden="true">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<path d="M7 10l5 5 5-5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7 10l5 5 5-5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
</svg>
</span>
</div>
@ -29,15 +31,15 @@
<div class="rcm-toolbar-right">
<div class="rcm-action-group">
<button type="button" class="btn rcm-icon-btn" data-tooltip="Add config" aria-label="Add">
<button type="button" class="btn rcm-icon-btn" data-tooltip="Add config" aria-label="Add" @onclick="OpenAddConfig">
<i class="mdi mdi-plus" aria-hidden="true"></i>
</button>
<button type="button" class="btn rcm-icon-btn" data-tooltip="Update config" aria-label="Update">
<button type="button" class="btn rcm-icon-btn" data-tooltip="Update config" aria-label="Update" @onclick="SaveConfig">
<i class="mdi mdi-content-save" aria-hidden="true"></i>
</button>
<button type="button" class="btn rcm-icon-btn rcm-danger" data-tooltip="Delete config" aria-label="Delete">
<button type="button" class="btn rcm-icon-btn rcm-danger" data-tooltip="Delete config" aria-label="Delete" @onclick="DeleteConfig">
<i class="mdi mdi-delete" aria-hidden="true"></i>
</button>
</div>
@ -49,19 +51,19 @@
@switch (SelectedType)
{
case RobotConfigType.VDA5050:
@RenderList(VdaConfigs, () => SelectVda)
@RenderList(VdaConfigs, SelectVda, v => v.ConfigName, v => v.IsActive)
break;
case RobotConfigType.Safety:
@RenderList(SafetyConfigs, () => SelectSafety)
@RenderList(SafetyConfigs, SelectSafety, s => s.ConfigName, s => s.IsActive)
break;
case RobotConfigType.Simulation:
@RenderList(SimulationConfigs, () => SelectSimulation)
@RenderList(SimulationConfigs, SelectSimulation, s => s.ConfigName, s => s.IsActive)
break;
case RobotConfigType.PLC:
@RenderList(PlcConfigs, () => SelectPlc)
@RenderList(PlcConfigs, SelectPlc, p => p.ConfigName, p => p.IsActive)
break;
case RobotConfigType.Core:
@RenderList(CoreConfigs, () => SelectCore)
@RenderList(CoreConfigs, SelectCore, c => c.ConfigName, c => c.IsActive)
break;
default:
<div class="p-2">No configs.</div>
@ -100,14 +102,93 @@
</div>
</div>
</div>
@if (IsLoading)
{
<div class="rcm-overlay" role="status" aria-live="polite" aria-busy="true">
<div class="rcm-overlay-content" aria-hidden="false">
<div class="spinner-border text-light" role="status" aria-hidden="true"></div>
<div class="rcm-overlay-message ms-2">Loading…</div>
</div>
</div>
}
@if (IsAddingNew)
{
<div class="rcm-modal-overlay" role="dialog" aria-modal="true">
<div class="rcm-modal">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<strong>Create @SelectedType Config</strong>
<button class="btn btn-sm btn-link text-muted" @onclick="CloseAddDialog" aria-label="Close">✕</button>
</div>
<div class="card-body">
<EditForm Model="addForm" OnValidSubmit="SaveNewConfigAsync">
<DataAnnotationsValidator />
<ValidationSummary />
<div class="mb-2">
<label class="form-label">Config name</label>
<InputText class="form-control" @bind-Value="addForm.ConfigName" />
</div>
<div class="mb-2">
<label class="form-label">Description</label>
<InputText class="form-control" @bind-Value="addForm.Description" />
</div>
<div class="mb-2">
<label class="form-label">Copy parameters from existing (@SelectedType)</label>
<select class="form-select" @bind="SelectedTemplateIdString">
@foreach (var t in GetTemplatesForSelectedType())
{
<option value="@t.Id">@t.Name</option>
}
</select>
<div class="form-text">If you choose an existing config, its parameter fields will be copied into the new config; you can still change name/description.</div>
</div>
<div class="d-flex justify-content-end gap-2 mt-3">
<button type="button" class="btn btn-outline-secondary" @onclick="CloseAddDialog">Cancel</button>
<button type="submit" class="btn btn-primary">Create</button>
</div>
</EditForm>
</div>
</div>
</div>
</div>
}
@if (ShowDeleteConfirm)
{
<div class="rcm-modal-overlay" role="dialog" aria-modal="true">
<div class="rcm-modal">
<div class="card">
<div class="card-header d-flex justify-content-between align-items-center">
<strong>Confirm Delete</strong>
<button class="btn btn-sm btn-link text-muted" @onclick="CancelDelete" aria-label="Close">✕</button>
</div>
<div class="card-body">
<p>Are you sure you want to delete configuration "<strong>@DeletePendingName</strong>"?</p>
<div class="d-flex justify-content-end gap-2 mt-3">
<button class="btn btn-outline-secondary" @onclick="CancelDelete">Cancel</button>
<button class="btn btn-danger" @onclick="ConfirmDeleteAsync">Delete</button>
</div>
</div>
</div>
</div>
</div>
}
</div>
@code {
private List<RobotVDA5050ConfigDto> VdaConfigs { get; } = [];
private List<RobotSafetyConfigDto> SafetyConfigs { get; } = [];
private List<RobotSimulationConfigDto> SimulationConfigs { get; } = [];
private List<RobotPlcConfigDto> PlcConfigs { get; } = [];
private List<RobotConfigDto> CoreConfigs { get; } = [];
private List<RobotVDA5050ConfigDto> VdaConfigs { get; set; } = new();
private List<RobotSafetyConfigDto> SafetyConfigs { get; set; } = new();
private List<RobotSimulationConfigDto> SimulationConfigs { get; set; } = new();
private List<RobotPlcConfigDto> PlcConfigs { get; set; } = new();
private List<RobotConfigDto> CoreConfigs { get; set; } = new();
private RobotVDA5050ConfigDto? SelectedVda { get; set; }
private RobotSafetyConfigDto? SelectedSafety { get; set; }
@ -115,13 +196,96 @@
private RobotPlcConfigDto? SelectedPlc { get; set; }
private RobotConfigDto? SelectedCore { get; set; }
private RobotConfigType SelectedType { get; set; } = RobotConfigType.VDA5050;
private CreateRobotVDA5050ConfigDto CreateVda = new();
private CreateRobotConfigDto CreateSafety = new();
private CreateRobotSafetyConfigDto CreateSimulation = new();
private CreateRobotSimulationConfigDto CreatePlc = new();
private CreateRobotPlcConfigDto CreateCore = new();
private int SelectedIndex { get; set; } = -1;
private RobotConfigType SelectedType = RobotConfigType.VDA5050;
private int SelectedIndex = -1;
private bool HasSelection => SelectedIndex >= 0;
private bool IsLoading = false;
private bool IsAddingNew = false;
RenderFragment RenderList<T>(List<T> list, Func<Action<int>> selectFactory) where T : class
private bool ShowDeleteConfirm = false;
private Guid? DeletePendingId;
private string DeletePendingName = string.Empty;
private class AddFormModel
{
public string ConfigName { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
}
private AddFormModel addForm = new();
private string SelectedTemplateIdString = string.Empty;
private IEnumerable<(Guid Id, string Name, bool Active)> GetTemplatesForSelectedType()
{
return SelectedType switch
{
RobotConfigType.VDA5050 => VdaConfigs.Select(x => (x.Id, x.ConfigName ?? string.Empty, x.IsActive)),
RobotConfigType.Safety => SafetyConfigs.Select(x => (x.Id, x.ConfigName ?? string.Empty, x.IsActive)),
RobotConfigType.Simulation => SimulationConfigs.Select(x => (x.Id, x.ConfigName ?? string.Empty, x.IsActive)),
RobotConfigType.PLC => PlcConfigs.Select(x => (x.Id, x.ConfigName ?? string.Empty, x.IsActive)),
RobotConfigType.Core => CoreConfigs.Select(x => (x.Id, x.ConfigName ?? string.Empty, x.IsActive)),
_ => [],
};
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await base.OnAfterRenderAsync(firstRender);
if (!firstRender) return;
await LoadForTypeAsync(SelectedType);
}
private async Task OnTypeChanged(ChangeEventArgs e)
{
if (e?.Value is null) return;
if (!Enum.TryParse<RobotConfigType>(e.Value.ToString(), out var newType)) return;
if (newType == SelectedType) return;
SelectedType = newType;
await LoadForTypeAsync(newType);
}
private async Task LoadForTypeAsync(RobotConfigType type)
{
IsLoading = true;
StateHasChanged();
try
{
switch (type)
{
case RobotConfigType.VDA5050:
await LoadVDA5050Configs();
break;
case RobotConfigType.Safety:
await LoadRobotSafetyConfigs();
break;
case RobotConfigType.Simulation:
await LoadRobotSimulationConfigs();
break;
case RobotConfigType.PLC:
await LoadRobotPlcConfigs();
break;
case RobotConfigType.Core:
await LoadRobotConfigs();
break;
}
}
finally
{
IsLoading = false;
StateHasChanged();
}
}
RenderFragment RenderList<T>(List<T> list, Action<int> onSelect, Func<T, string> nameSelector, Func<T, bool>? isActiveSelector = null) where T : class
{
return builder =>
{
@ -141,24 +305,40 @@
{
var item = list[i];
var idx = i;
builder.OpenElement(10 + i * 5, "li");
builder.AddAttribute(11 + i * 5, "class", $"list-group-item {(SelectedIndex==idx ? "active" : "")}");
builder.AddAttribute(12 + i * 5, "style", "cursor:pointer;padding:0.75rem 1rem;");
builder.AddAttribute(13 + i * 5, "onclick", EventCallback.Factory.Create(this, () => selectFactory()(idx)));
builder.OpenElement(10 + i * 6, "li");
builder.AddAttribute(11 + i * 6, "class", $"list-group-item {(SelectedIndex == idx ? "active" : "")}");
builder.AddAttribute(12 + i * 6, "style", "cursor:pointer;padding:0.75rem 1rem;");
builder.AddAttribute(13 + i * 6, "onclick", EventCallback.Factory.Create(this, () => onSelect(idx)));
// Only show ConfigName
var nameProp = item.GetType().GetProperty("ConfigName");
var name = nameProp?.GetValue(item)?.ToString() ?? "Unnamed";
builder.AddContent(14 + i * 5, name);
string name;
try
{
name = nameSelector(item) ?? "Unnamed";
}
catch
{
name = "Unnamed";
}
builder.AddContent(14 + i * 6, name);
bool isActive = false;
if (isActiveSelector is not null)
{
try
{
isActive = isActiveSelector(item);
}
catch
{
isActive = false;
}
}
// Show active badge at end if IsActive == true
var isActiveProp = item.GetType().GetProperty("IsActive");
var isActive = isActiveProp?.GetValue(item) is bool b && b;
if (isActive)
{
builder.OpenElement(15 + i * 5, "span");
builder.AddAttribute(16 + i * 5, "class", "badge bg-success ms-2 float-end");
builder.AddContent(17 + i * 5, "Active");
builder.OpenElement(15 + i * 6, "span");
builder.AddAttribute(16 + i * 6, "class", "badge bg-success ms-2 float-end");
builder.AddContent(17 + i * 6, "Active");
builder.CloseElement();
}
@ -204,5 +384,47 @@
StateHasChanged();
};
private void OpenAddConfig()
{
addForm = new AddFormModel();
var model = GetTemplatesForSelectedType();
var modelActive = model.Where(x => x.Active).ToList();
SelectedTemplateIdString = modelActive.Count > 0 ? modelActive.First().Id.ToString() : model.Any() ? model.First().Id.ToString() : string.Empty;
IsAddingNew = true;
}
private void CloseAddDialog()
{
IsAddingNew = false;
}
private void DeleteConfig()
{
var tuple = SelectedType switch
{
RobotConfigType.VDA5050 => (Id: SelectedVda?.Id, Name: SelectedVda?.ConfigName),
RobotConfigType.Safety => (Id: SelectedSafety?.Id, Name: SelectedSafety?.ConfigName),
RobotConfigType.Simulation => (Id: SelectedSimulation?.Id, Name: SelectedSimulation?.ConfigName),
RobotConfigType.PLC => (Id: SelectedPlc?.Id, Name: SelectedPlc?.ConfigName),
RobotConfigType.Core => (Id: SelectedCore?.Id, Name: SelectedCore?.ConfigName),
_ => (Id: (Guid?)null, Name: (string?)null)
};
if (tuple.Id is null || tuple.Id == Guid.Empty)
{
Snackbar.Add("No config selected to delete.", Severity.Warning);
return;
}
DeletePendingId = tuple.Id;
DeletePendingName = tuple.Name ?? string.Empty;
ShowDeleteConfirm = true;
}
private void CancelDelete()
{
ShowDeleteConfirm = false;
DeletePendingId = null;
DeletePendingName = string.Empty;
}
}

View File

@ -0,0 +1,521 @@
using MudBlazor;
using RobotApp.Common.Shares;
using RobotApp.Common.Shares.Dtos;
using RobotApp.Common.Shares.Enums;
using System.Net.Http.Json;
using System.Xml.Schema;
namespace RobotApp.Client.Pages;
public partial class RobotConfigManager
{
private async Task LoadVDA5050Configs()
{
try
{
var res = await Http.GetFromJsonAsync<MessageResult<RobotVDA5050ConfigDto[]>>("api/RobotConfigs/vda5050");
if (res is null) Snackbar.Add("Failed to load VDA5050 configs", Severity.Warning);
else if (!res.IsSuccess) Snackbar.Add(res.Message ?? "Failed to load VDA5050 configs", Severity.Warning);
else if (res.Data is not null)
{
VdaConfigs.Clear();
VdaConfigs.AddRange(res.Data);
var activeIdx = VdaConfigs.FindIndex(x => x.IsActive);
if (activeIdx >= 0)
{
SelectedIndex = activeIdx;
SelectedVda = VdaConfigs[activeIdx] with { };
}
else
{
SelectedIndex = -1;
SelectedVda = null;
}
StateHasChanged();
}
else
{
Snackbar.Add("No VDA5050 configs found", Severity.Info);
}
}
catch (Exception ex)
{
Snackbar.Add($"Error loading PLC configs: {ex.Message}", Severity.Warning);
}
}
private async Task LoadRobotConfigs()
{
try
{
var res = await Http.GetFromJsonAsync<MessageResult<RobotConfigDto[]>>("api/RobotConfigs/robot");
if (res is null) Snackbar.Add("Failed to load VDA5050 configs", Severity.Warning);
else if (!res.IsSuccess) Snackbar.Add(res.Message ?? "Failed to load VDA5050 configs", Severity.Warning);
else if (res.Data is not null)
{
CoreConfigs.Clear();
CoreConfigs.AddRange(res.Data);
var activeIdx = CoreConfigs.FindIndex(x => x.IsActive);
if (activeIdx >= 0)
{
SelectedIndex = activeIdx;
SelectedCore = CoreConfigs[activeIdx] with { };
}
else
{
SelectedIndex = -1;
SelectedCore = null;
}
StateHasChanged();
}
}
catch (Exception ex)
{
Snackbar.Add($"Error loading PLC configs: {ex.Message}", Severity.Warning);
}
}
private async Task LoadRobotSafetyConfigs()
{
try
{
var res = await Http.GetFromJsonAsync<MessageResult<RobotSafetyConfigDto[]>>("api/RobotConfigs/safety");
if (res is null) Snackbar.Add("Failed to load VDA5050 configs", Severity.Warning);
else if (!res.IsSuccess) Snackbar.Add(res.Message ?? "Failed to load VDA5050 configs", Severity.Warning);
else if (res.Data is not null)
{
SafetyConfigs.Clear();
SafetyConfigs.AddRange(res.Data);
var activeIdx = SafetyConfigs.FindIndex(x => x.IsActive);
if (activeIdx >= 0)
{
SelectedIndex = activeIdx;
SelectedSafety = SafetyConfigs[activeIdx] with { };
}
else
{
SelectedIndex = -1;
SelectedSafety = null;
}
StateHasChanged();
}
}
catch (Exception ex)
{
Snackbar.Add($"Error loading PLC configs: {ex.Message}", Severity.Warning);
}
}
private async Task LoadRobotSimulationConfigs()
{
try
{
var res = await Http.GetFromJsonAsync<MessageResult<RobotSimulationConfigDto[]>>("api/RobotConfigs/simulation");
if (res is null) Snackbar.Add("Failed to load VDA5050 configs", Severity.Warning);
else if (!res.IsSuccess) Snackbar.Add(res.Message ?? "Failed to load VDA5050 configs", Severity.Warning);
else if (res.Data is not null)
{
SimulationConfigs.Clear();
SimulationConfigs.AddRange(res.Data);
var activeIdx = SimulationConfigs.FindIndex(x => x.IsActive);
if (activeIdx >= 0)
{
SelectedIndex = activeIdx;
SelectedSimulation = SimulationConfigs[activeIdx] with { };
}
else
{
SelectedIndex = -1;
SelectedSimulation = null;
}
StateHasChanged();
}
}
catch (Exception ex)
{
Snackbar.Add($"Error loading PLC configs: {ex.Message}", Severity.Warning);
}
}
private async Task LoadRobotPlcConfigs()
{
try
{
var res = await Http.GetFromJsonAsync<MessageResult<RobotPlcConfigDto[]>>("api/RobotConfigs/plc");
if (res is null) Snackbar.Add("Failed to load VDA5050 configs", Severity.Warning);
else if (!res.IsSuccess) Snackbar.Add(res.Message ?? "Failed to load VDA5050 configs", Severity.Warning);
else if (res.Data is not null)
{
PlcConfigs.Clear();
PlcConfigs.AddRange(res.Data);
var activeIdx = PlcConfigs.FindIndex(x => x.IsActive);
if (activeIdx >= 0)
{
SelectedIndex = activeIdx;
SelectedPlc = PlcConfigs[activeIdx] with { };
}
else
{
SelectedIndex = -1;
SelectedPlc = null;
}
StateHasChanged();
}
}
catch (Exception ex)
{
Snackbar.Add($"Error loading PLC configs: {ex.Message}", Severity.Warning);
}
}
private async Task SaveNewConfigAsync()
{
try
{
HttpResponseMessage? res = null;
_ = Guid.TryParse(SelectedTemplateIdString, out Guid templateId);
switch (SelectedType)
{
case RobotConfigType.VDA5050:
{
var template = VdaConfigs.FirstOrDefault(x => x.Id == templateId);
if (template is null) return;
var payload = new
{
addForm.ConfigName,
addForm.Description,
template.SerialNumber,
template.VDA5050HostServer,
template.VDA5050Port,
template.VDA5050UserName,
template.VDA5050Password,
template.VDA5050Manufacturer,
template.VDA5050Version,
template.VDA5050PublishRepeat,
template.VDA5050EnablePassword,
template.VDA5050EnableTls
};
res = await Http.PostAsJsonAsync("api/RobotConfigs/vda5050", payload);
break;
}
case RobotConfigType.PLC:
{
var template = PlcConfigs.FirstOrDefault(x => x.Id == templateId);
if (template is null) return;
var payload = new
{
addForm.ConfigName,
addForm.Description,
template.PLCAddress,
template.PLCPort,
template.PLCUnitId
};
res = await Http.PostAsJsonAsync("api/RobotConfigs/plc", payload);
break;
}
case RobotConfigType.Safety:
{
var template = SafetyConfigs.FirstOrDefault(x => x.Id == templateId);
if (template is null) return;
var payload = new
{
addForm.ConfigName,
addForm.Description,
template.SafetySpeedVerySlow,
template.SafetySpeedSlow,
template.SafetySpeedNormal,
template.SafetySpeedMedium,
template.SafetySpeedOptimal,
template.SafetySpeedFast,
template.SafetySpeedVeryFast
};
res = await Http.PostAsJsonAsync("api/RobotConfigs/safety", payload);
break;
}
case RobotConfigType.Simulation:
{
var template = SimulationConfigs.FirstOrDefault(x => x.Id == templateId);
if (template is null) return;
var payload = new
{
addForm.ConfigName,
addForm.Description,
template.EnableSimulation,
template.SimulationMaxVelocity,
template.SimulationMaxAngularVelocity,
template.SimulationAcceleration,
template.SimulationDeceleration
};
res = await Http.PostAsJsonAsync("api/RobotConfigs/simulation", payload);
break;
}
case RobotConfigType.Core:
default:
{
var template = CoreConfigs.FirstOrDefault(x => x.Id == templateId);
if (template is null) return;
var payload = new
{
addForm.ConfigName,
addForm.Description,
template.NavigationType,
template.RadiusWheel,
template.Width,
template.Length,
template.Height
};
res = await Http.PostAsJsonAsync("api/RobotConfigs/robot", payload);
break;
}
}
if (res is not null && res.IsSuccessStatusCode)
{
Snackbar.Add("Config created", Severity.Success);
IsAddingNew = false;
await LoadForTypeAsync(SelectedType);
}
else
{
var message = res is null ? "No response" : await res.Content.ReadAsStringAsync();
Snackbar.Add($"Create failed: {message}", Severity.Error);
}
}
catch (Exception ex)
{
Snackbar.Add($"Error creating config: {ex.Message}", Severity.Error);
}
}
private async Task SaveConfig()
{
try
{
Guid? id = SelectedType switch
{
RobotConfigType.VDA5050 => SelectedVda?.Id,
RobotConfigType.Safety => SelectedSafety?.Id,
RobotConfigType.Simulation => SelectedSimulation?.Id,
RobotConfigType.PLC => SelectedPlc?.Id,
RobotConfigType.Core => SelectedCore?.Id,
_ => null
};
if (id == null || id == Guid.Empty)
{
Snackbar.Add("No config selected to save.", Severity.Warning);
return;
}
MessageResult? result = null;
switch (SelectedType)
{
case RobotConfigType.VDA5050:
{
if (SelectedVda is null) { Snackbar.Add("No VDA5050 config selected.", Severity.Warning); return; }
var updateDto = new
{
SelectedVda.SerialNumber,
SelectedVda.VDA5050HostServer,
SelectedVda.VDA5050Port,
SelectedVda.VDA5050UserName,
SelectedVda.VDA5050Password,
SelectedVda.VDA5050Manufacturer,
SelectedVda.VDA5050Version,
SelectedVda.VDA5050PublishRepeat,
SelectedVda.VDA5050EnablePassword,
SelectedVda.VDA5050EnableTls,
SelectedVda.Description
};
result = await (await Http.PutAsJsonAsync($"api/RobotConfigs/vda5050/{id}", updateDto)).Content.ReadFromJsonAsync<MessageResult>();
break;
}
case RobotConfigType.PLC:
{
if (SelectedPlc is null) { Snackbar.Add("No PLC config selected.", Severity.Warning); return; }
var updateDto = new
{
SelectedPlc.Description,
SelectedPlc.PLCAddress,
SelectedPlc.PLCPort,
SelectedPlc.PLCUnitId
};
result = await (await Http.PutAsJsonAsync($"api/RobotConfigs/plc/{id}", updateDto)).Content.ReadFromJsonAsync<MessageResult>();
break;
}
case RobotConfigType.Safety:
{
if (SelectedSafety is null) { Snackbar.Add("No Safety config selected.", Severity.Warning); return; }
var updateDto = new
{
SelectedSafety.SafetySpeedVerySlow,
SelectedSafety.SafetySpeedSlow,
SelectedSafety.SafetySpeedNormal,
SelectedSafety.SafetySpeedMedium,
SelectedSafety.SafetySpeedOptimal,
SelectedSafety.SafetySpeedFast,
SelectedSafety.SafetySpeedVeryFast,
SelectedSafety.Description
};
result = await (await Http.PutAsJsonAsync($"api/RobotConfigs/safety/{id}", updateDto)).Content.ReadFromJsonAsync<MessageResult>();
break;
}
case RobotConfigType.Simulation:
{
if (SelectedSimulation is null) { Snackbar.Add("No Simulation config selected.", Severity.Warning); return; }
var updateDto = new
{
SelectedSimulation.EnableSimulation,
SelectedSimulation.SimulationMaxVelocity,
SelectedSimulation.SimulationMaxAngularVelocity,
SelectedSimulation.SimulationAcceleration,
SelectedSimulation.SimulationDeceleration,
SelectedSimulation.Description
};
result = await (await Http.PutAsJsonAsync($"api/RobotConfigs/simulation/{id}", updateDto)).Content.ReadFromJsonAsync<MessageResult>();
break;
}
case RobotConfigType.Core:
default:
{
if (SelectedCore is null) { Snackbar.Add("No Core config selected.", Severity.Warning); return; }
var updateDto = new
{
SelectedCore.NavigationType,
SelectedCore.RadiusWheel,
SelectedCore.Width,
SelectedCore.Length,
SelectedCore.Height,
SelectedCore.Description
};
result = await (await Http.PutAsJsonAsync($"api/RobotConfigs/robot/{id}", updateDto)).Content.ReadFromJsonAsync<MessageResult>();
break;
}
}
if (result is null) Snackbar.Add("Failed to update config", Severity.Warning);
else if (!result.IsSuccess) Snackbar.Add(result.Message ?? "Failed to update config", Severity.Warning);
else
{
Snackbar.Add("Config saved", Severity.Success);
await LoadForTypeAsync(SelectedType);
switch (SelectedType)
{
case RobotConfigType.VDA5050:
var vIdx = VdaConfigs.FindIndex(x => x.Id == id);
if (vIdx >= 0) { SelectedIndex = vIdx; SelectedVda = VdaConfigs[vIdx] with { }; }
break;
case RobotConfigType.Safety:
var sIdx = SafetyConfigs.FindIndex(x => x.Id == id);
if (sIdx >= 0) { SelectedIndex = sIdx; SelectedSafety = SafetyConfigs[sIdx] with { }; }
break;
case RobotConfigType.Simulation:
var simIdx = SimulationConfigs.FindIndex(x => x.Id == id);
if (simIdx >= 0) { SelectedIndex = simIdx; SelectedSimulation = SimulationConfigs[simIdx] with { }; }
break;
case RobotConfigType.PLC:
var pIdx = PlcConfigs.FindIndex(x => x.Id == id);
if (pIdx >= 0) { SelectedIndex = pIdx; SelectedPlc = PlcConfigs[pIdx] with { }; }
break;
case RobotConfigType.Core:
var cIdx = CoreConfigs.FindIndex(x => x.Id == id);
if (cIdx >= 0) { SelectedIndex = cIdx; SelectedCore = CoreConfigs[cIdx] with { }; }
break;
}
StateHasChanged();
}
}
catch (Exception ex)
{
Snackbar.Add($"Error saving config: {ex.Message}", Severity.Error);
}
}
private async Task ConfirmDeleteAsync()
{
try
{
if (DeletePendingId is null || DeletePendingId == Guid.Empty)
{
Snackbar.Add("No config selected to delete.", Severity.Warning);
CancelDelete();
return;
}
var id = DeletePendingId.Value;
string path = SelectedType switch
{
RobotConfigType.VDA5050 => $"api/RobotConfigs/vda5050/{id}",
RobotConfigType.Safety => $"api/RobotConfigs/safety/{id}",
RobotConfigType.Simulation => $"api/RobotConfigs/simulation/{id}",
RobotConfigType.PLC => $"api/RobotConfigs/plc/{id}",
RobotConfigType.Core => $"api/RobotConfigs/robot/{id}",
_ => throw new InvalidOperationException("Unsupported config type")
};
IsLoading = true;
StateHasChanged();
var httpRes = await Http.DeleteFromJsonAsync<MessageResult>(path);
if (httpRes is null) Snackbar.Add("Failed to delete config", Severity.Warning);
else if (!httpRes.IsSuccess) Snackbar.Add(httpRes.Message ?? "Failed to delete config", Severity.Warning);
else
{
Snackbar.Add("Config deleted", Severity.Success);
SelectedIndex = -1;
switch (SelectedType)
{
case RobotConfigType.VDA5050: SelectedVda = null; break;
case RobotConfigType.Safety: SelectedSafety = null; break;
case RobotConfigType.Simulation: SelectedSimulation = null; break;
case RobotConfigType.PLC: SelectedPlc = null; break;
case RobotConfigType.Core: SelectedCore = null; break;
}
await LoadForTypeAsync(SelectedType);
}
}
catch (Exception ex)
{
Snackbar.Add($"Error deleting config: {ex.Message}", Severity.Error);
}
finally
{
IsLoading = false;
CancelDelete();
StateHasChanged();
}
}
}

View File

@ -4,7 +4,7 @@
gap: 1rem;
align-items: center;
justify-content: space-between;
background: linear-gradient(180deg, rgba(30,30,30,0.95), rgba(20,20,20,0.95));
background: var(--mud-palette-surface, #ffffff);
color: #e6e6e6;
padding: 0.75rem 1rem;
border-radius: 0.375rem;
@ -373,3 +373,148 @@
display: inline-block;
vertical-align: middle;
}
/* Overlay that blocks the whole viewport while loading */
.rcm-overlay {
position: absolute;
inset: 0; /* top:0; right:0; bottom:0; left:0; */
display: flex;
align-items: center;
justify-content: center;
background: rgba(7, 10, 13, 0.55);
z-index: 1050; /* above most UI layers */
pointer-events: auto; /* capture mouse interactions */
}
/* inner content: spinner + optional message */
.rcm-overlay-content {
display: inline-flex;
align-items: center;
gap: 0.6rem;
background: rgba(20, 20, 20, 0.85);
padding: 0.75rem 1rem;
border-radius: 8px;
box-shadow: 0 8px 24px rgba(0,0,0,0.45);
color: #fff;
font-weight: 500;
transform: translateY(-6px);
}
/* message text */
.rcm-overlay-message {
font-size: 0.95rem;
color: #eef3ff;
}
/* ensure spinner is visible on dark overlay */
.rcm-overlay .spinner-border {
width: 1.25rem;
height: 1.25rem;
border-width: 0.18rem;
color: #ffffff;
}
/* Modal overlay (create dialog) */
.rcm-modal-overlay {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
background: rgba(7, 10, 13, 0.45);
z-index: 1065;
pointer-events: auto;
}
/* Modal container */
.rcm-modal {
width: 820px;
max-width: calc(100% - 40px);
border-radius: 8px;
box-shadow: 0 12px 40px rgba(0,0,0,0.5);
background: var(--mud-palette-surface, #0f1720);
color: #e6e6e6;
}
/* Ensure card inside modal uses default spacing already present */
.rcm-modal .card {
background: #0f1720;
border: 1px solid rgba(255,255,255,0.04);
}
/* Header styling */
.rcm-modal .card-header {
padding: 0.6rem 0.9rem;
background: rgba(255,255,255,0.02);
border-bottom: 1px solid rgba(255,255,255,0.03);
}
/* Modal message / spinner adjustments (already defined for overlay) */
.rcm-overlay .spinner-border {
width: 1.25rem;
height: 1.25rem;
border-width: 0.18rem;
color: #ffffff;
}
/* Keep responsive layout for modal form inputs */
.rcm-modal .card-body {
padding: 0.8rem;
color: #e6e6e6;
}
/* --- Contrast adjustments for modal form elements --- */
/* Improve readability by increasing text contrast and adjusting input backgrounds */
.rcm-modal {
color: #eaf4ff; /* default text inside modal */
}
/* labels, headers and small notes */
.rcm-modal .form-label,
.rcm-modal label,
.rcm-modal .card-header strong,
.rcm-modal .card-header,
.rcm-modal .form-text,
.rcm-modal .validation-message,
.rcm-modal .text-muted {
color: #cfe6ff;
}
/* form controls */
.rcm-modal .form-control,
.rcm-modal .form-select,
.rcm-modal input[type="text"],
.rcm-modal input[type="number"],
.rcm-modal textarea {
/*background: rgba(255,255,255,0.03);*/ /* subtle contrast with modal bg */
/*color: #ffffff;*/
border: 1px solid rgba(255,255,255,0.08);
box-shadow: none;
caret-color: #ffffff;
}
/* placeholder text */
.rcm-modal .form-control::placeholder,
.rcm-modal input::placeholder,
.rcm-modal textarea::placeholder {
color: rgba(255,255,255,0.55);
}
/* validation / summary messages */
.rcm-modal .validation-message,
.rcm-modal .validation-summary-valid,
.rcm-modal .validation-summary-errors,
.rcm-modal .field-validation-error {
color: #ffd2d2;
}
/* modal buttons */
.rcm-modal .btn-link {
color: #cfcfcf;
}
/* keep modal small text readable */
.rcm-modal .card-body {
font-size: 0.95rem;
line-height: 1.4;
}

View File

@ -26,17 +26,16 @@ public record UpdateRobotConfigDto
public double Width { get; set; }
public double Length { get; set; }
public double Height { get; set; }
public string ConfigName { get; set; }
public string Description { get; set; }
}
public record CreateRobotConfigDto
{
public string ConfigName { get; set; }
public string Description { get; set; }
public NavigationType NavigationType { get; set; }
public double RadiusWheel { get; set; }
public double Width { get; set; }
public double Length { get; set; }
public double Height { get; set; }
public string ConfigName { get; set; }
public string Description { get; set; }
}

View File

@ -17,7 +17,6 @@ public record RobotPlcConfigDto
public record UpdateRobotPlcConfigDto
{
public string ConfigName { get; set; }
public string Description { get; set; }
public string PLCAddress { get; set; }
public int PLCPort { get; set; }

View File

@ -28,7 +28,6 @@ public record UpdateRobotSafetyConfigDto
public double SafetySpeedOptimal { get; set; }
public double SafetySpeedFast { get; set; }
public double SafetySpeedVeryFast { get; set; }
public string ConfigName { get; set; }
public string Description { get; set; }
}

View File

@ -24,7 +24,6 @@ public record UpdateRobotSimulationConfigDto
public double SimulationMaxAngularVelocity { get; set; }
public double SimulationAcceleration { get; set; }
public double SimulationDeceleration { get; set; }
public string ConfigName { get; set; }
public string Description { get; set; }
}

View File

@ -1,13 +1,19 @@
namespace RobotApp.Common.Shares.Dtos;
using System.ComponentModel.DataAnnotations;
namespace RobotApp.Common.Shares.Dtos;
#nullable disable
public record RobotVDA5050ConfigDto
{
public Guid Id { get; set; }
[Required]
public string SerialNumber { get; set; }
[Required]
public string VDA5050HostServer { get; set; }
[Required]
public int VDA5050Port { get; set; }
[Required]
public string VDA5050UserName { get; set; }
public string VDA5050Password { get; set; }
public string VDA5050Manufacturer { get; set; }
@ -15,6 +21,9 @@ public record RobotVDA5050ConfigDto
public int VDA5050PublishRepeat { get; set; }
public bool VDA5050EnablePassword { get; set; }
public bool VDA5050EnableTls { get; set; }
public string VDA5050CA { get; set; }
public string VDA5050Cer { get; set; }
public string VDA5050_Key { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
public bool IsActive { get; set; }
@ -34,7 +43,9 @@ public record UpdateRobotVDA5050ConfigDto
public int VDA5050PublishRepeat { get; set; }
public bool VDA5050EnablePassword { get; set; }
public bool VDA5050EnableTls { get; set; }
public string ConfigName { get; set; }
public string VDA5050CA { get; set; }
public string VDA5050Cer { get; set; }
public string VDA5050_Key { get; set; }
public string Description { get; set; }
}
@ -50,6 +61,9 @@ public record CreateRobotVDA5050ConfigDto
public int VDA5050PublishRepeat { get; set; }
public bool VDA5050EnablePassword { get; set; }
public bool VDA5050EnableTls { get; set; }
public string VDA5050CA { get; set; }
public string VDA5050Cer { get; set; }
public string VDA5050_Key { get; set; }
public string ConfigName { get; set; }
public string Description { get; set; }
}

View File

@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using RobotApp.Common.Shares;
@ -49,18 +50,13 @@ public class RobotConfigsController(Services.Logger<RobotConfigsController> Logg
[HttpPut]
[Route("plc/{id:guid}")]
public async Task<MessageResult<RobotPlcConfigDto>> UpdatePLCConfig(Guid id, [FromBody] UpdateRobotPlcConfigDto updateDto)
public async Task<MessageResult> UpdatePLCConfig(Guid id, [FromBody] UpdateRobotPlcConfigDto updateDto)
{
try
{
var config = await AppDb.RobotPlcConfigs.FindAsync(id);
if (config is null) return new(false, "PLC configuration not found.");
if (string.IsNullOrEmpty(updateDto.ConfigName)) return new(false, "ConfigName cannot be null or empty.");
if (await AppDb.RobotPlcConfigs.AnyAsync(cf => cf.Id != id && cf.ConfigName == updateDto.ConfigName))
return new(false, "A PLC configuration with the same name already exists.");
config.ConfigName = updateDto.ConfigName ?? config.ConfigName;
config.Description = updateDto.Description ?? config.Description;
config.PLCAddress = updateDto.PLCAddress ?? config.PLCAddress;
config.PLCPort = updateDto.PLCPort;
@ -70,21 +66,7 @@ public class RobotConfigsController(Services.Logger<RobotConfigsController> Logg
await AppDb.SaveChangesAsync();
if (config.IsActive) await RobotConfiguration.LoadRobotPlcConfigAsync();
return new(true, "PLC configuration updated successfully.")
{
Data = new RobotPlcConfigDto
{
Id = config.Id,
ConfigName = config.ConfigName,
Description = config.Description,
PLCAddress = config.PLCAddress,
PLCPort = config.PLCPort,
PLCUnitId = config.PLCUnitId,
CreatedAt = config.CreatedAt,
UpdatedAt = config.UpdatedAt,
IsActive = config.IsActive,
}
};
return new(true, "PLC configuration updated successfully.");
}
catch (Exception ex)
{
@ -150,6 +132,7 @@ public class RobotConfigsController(Services.Logger<RobotConfigsController> Logg
var config = await AppDb.RobotPlcConfigs.FindAsync(id);
if (config is null) return new(false, "PLC configuration not found.");
if (config.IsActive) return new(false, "Cannot delete an active PLC configuration.");
if (config.ConfigName == "Default") return new(false, "Cannot delete the default PLC configuration.");
AppDb.RobotPlcConfigs.Remove(config);
await AppDb.SaveChangesAsync();
@ -211,6 +194,9 @@ public class RobotConfigsController(Services.Logger<RobotConfigsController> Logg
VDA5050PublishRepeat = config.VDA5050PublishRepeat,
VDA5050EnablePassword = config.VDA5050EnablePassword,
VDA5050EnableTls = config.VDA5050EnableTls,
VDA5050CA = config.VDA5050CA,
VDA5050Cer = config.VDA5050Cer,
VDA5050_Key = config.VDA5050_Key,
CreatedAt = config.CreatedAt,
UpdatedAt = config.UpdatedAt,
IsActive = config.IsActive,
@ -229,17 +215,13 @@ public class RobotConfigsController(Services.Logger<RobotConfigsController> Logg
[HttpPut]
[Route("vda5050/{id:guid}")]
public async Task<MessageResult<RobotVDA5050ConfigDto>> UpdateVDA5050Config(Guid id, [FromBody] UpdateRobotVDA5050ConfigDto updateDto)
public async Task<MessageResult> UpdateVDA5050Config(Guid id, [FromBody] UpdateRobotVDA5050ConfigDto updateDto)
{
try
{
var config = await AppDb.RobotVDA5050Configs.FindAsync(id);
if (config is null) return new(false, "VDA5050 configuration not found.");
if (string.IsNullOrEmpty(updateDto.ConfigName)) return new(false, "ConfigName cannot be null or empty.");
if (await AppDb.RobotVDA5050Configs.AnyAsync(cf => cf.Id != id && cf.ConfigName == updateDto.ConfigName))
return new(false, "A VDA5050 configuration with the same name already exists.");
config.SerialNumber = updateDto.SerialNumber ?? config.SerialNumber;
config.VDA5050HostServer = updateDto.VDA5050HostServer ?? config.VDA5050HostServer;
config.VDA5050Port = updateDto.VDA5050Port;
@ -250,35 +232,16 @@ public class RobotConfigsController(Services.Logger<RobotConfigsController> Logg
config.VDA5050PublishRepeat = updateDto.VDA5050PublishRepeat;
config.VDA5050EnablePassword = updateDto.VDA5050EnablePassword;
config.VDA5050EnableTls = updateDto.VDA5050EnableTls;
config.ConfigName = updateDto.ConfigName ?? config.ConfigName;
config.VDA5050CA = updateDto.VDA5050CA;
config.VDA5050Cer = updateDto.VDA5050Cer;
config.VDA5050_Key = updateDto.VDA5050_Key;
config.Description = updateDto.Description ?? config.Description;
config.UpdatedAt = DateTime.Now;
await AppDb.SaveChangesAsync();
if (config.IsActive) await RobotConfiguration.LoadVDA5050ConfigAsync();
return new(true, "VDA5050 configuration updated successfully.")
{
Data = new RobotVDA5050ConfigDto
{
Id = config.Id,
SerialNumber = config.SerialNumber,
VDA5050HostServer = config.VDA5050HostServer,
VDA5050Port = config.VDA5050Port,
VDA5050UserName = config.VDA5050UserName,
VDA5050Password = config.VDA5050Password,
VDA5050Manufacturer = config.VDA5050Manufacturer,
VDA5050Version = config.VDA5050Version,
VDA5050PublishRepeat = config.VDA5050PublishRepeat,
VDA5050EnablePassword = config.VDA5050EnablePassword,
VDA5050EnableTls = config.VDA5050EnableTls,
CreatedAt = config.CreatedAt,
UpdatedAt = config.UpdatedAt,
IsActive = config.IsActive,
ConfigName = config.ConfigName,
Description = config.Description
}
};
return new(true, "VDA5050 configuration updated successfully.");
}
catch (Exception ex)
{
@ -311,6 +274,9 @@ public class RobotConfigsController(Services.Logger<RobotConfigsController> Logg
VDA5050PublishRepeat = createDto.VDA5050PublishRepeat,
VDA5050EnablePassword = createDto.VDA5050EnablePassword,
VDA5050EnableTls = createDto.VDA5050EnableTls,
VDA5050CA = createDto.VDA5050CA,
VDA5050Cer = createDto.VDA5050Cer,
VDA5050_Key = createDto.VDA5050_Key,
CreatedAt = DateTime.Now,
UpdatedAt = DateTime.Now,
IsActive = false
@ -336,6 +302,9 @@ public class RobotConfigsController(Services.Logger<RobotConfigsController> Logg
VDA5050PublishRepeat = config.VDA5050PublishRepeat,
VDA5050EnablePassword = config.VDA5050EnablePassword,
VDA5050EnableTls = config.VDA5050EnableTls,
VDA5050CA = config.VDA5050CA,
VDA5050Cer = config.VDA5050Cer,
VDA5050_Key = config.VDA5050_Key,
CreatedAt = config.CreatedAt,
UpdatedAt = config.UpdatedAt,
IsActive = config.IsActive,
@ -358,6 +327,7 @@ public class RobotConfigsController(Services.Logger<RobotConfigsController> Logg
var config = await AppDb.RobotVDA5050Configs.FindAsync(id);
if (config is null) return new(false, "VDA5050 configuration not found.");
if (config.IsActive) return new(false, "Cannot delete an active VDA5050 configuration.");
if (config.ConfigName == "Default") return new(false, "Cannot delete the default PLC configuration.");
AppDb.RobotVDA5050Configs.Remove(config);
await AppDb.SaveChangesAsync();
@ -432,46 +402,25 @@ public class RobotConfigsController(Services.Logger<RobotConfigsController> Logg
[HttpPut]
[Route("robot/{id:guid}")]
public async Task<MessageResult<RobotConfigDto>> UpdateRobotConfig(Guid id, [FromBody] UpdateRobotConfigDto updateDto)
public async Task<MessageResult> UpdateRobotConfig(Guid id, [FromBody] UpdateRobotConfigDto updateDto)
{
try
{
var config = await AppDb.RobotConfigs.FindAsync(id);
if (config is null) return new(false, "Robot configuration not found.");
if (string.IsNullOrEmpty(updateDto.ConfigName)) return new(false, "ConfigName cannot be null or empty.");
if (await AppDb.RobotConfigs.AnyAsync(cf => cf.Id != id && cf.ConfigName == updateDto.ConfigName))
return new(false, "A Robot configuration with the same name already exists.");
config.NavigationType = updateDto.NavigationType;
config.RadiusWheel = updateDto.RadiusWheel;
config.Width = updateDto.Width;
config.Length = updateDto.Length;
config.Height = updateDto.Height;
config.ConfigName = updateDto.ConfigName ?? config.ConfigName;
config.Description = updateDto.Description ?? config.Description;
config.UpdatedAt = DateTime.Now;
await AppDb.SaveChangesAsync();
if (config.IsActive) await RobotConfiguration.LoadRobotConfigAsync();
return new(true, "Robot configuration updated successfully.")
{
Data = new RobotConfigDto
{
Id = config.Id,
NavigationType = config.NavigationType,
RadiusWheel = config.RadiusWheel,
Width = config.Width,
Length = config.Length,
Height = config.Height,
CreatedAt = config.CreatedAt,
UpdatedAt = config.UpdatedAt,
IsActive = config.IsActive,
ConfigName = config.ConfigName,
Description = config.Description
}
};
return new(true, "Robot configuration updated successfully.");
}
catch (Exception ex)
{
@ -541,6 +490,7 @@ public class RobotConfigsController(Services.Logger<RobotConfigsController> Logg
var config = await AppDb.RobotConfigs.FindAsync(id);
if (config is null) return new(false, "Robot configuration not found.");
if (config.IsActive) return new(false, "Cannot delete an active Robot configuration.");
if (config.ConfigName == "Default") return new(false, "Cannot delete the default PLC configuration.");
AppDb.RobotConfigs.Remove(config);
await AppDb.SaveChangesAsync();
@ -615,46 +565,25 @@ public class RobotConfigsController(Services.Logger<RobotConfigsController> Logg
[HttpPut]
[Route("simulation/{id:guid}")]
public async Task<MessageResult<RobotSimulationConfigDto>> UpdateSimulationConfig(Guid id, [FromBody] UpdateRobotSimulationConfigDto updateDto)
public async Task<MessageResult> UpdateSimulationConfig(Guid id, [FromBody] UpdateRobotSimulationConfigDto updateDto)
{
try
{
var config = await AppDb.RobotSimulationConfigs.FindAsync(id);
if (config is null) return new(false, "Simulation configuration not found.");
if (string.IsNullOrEmpty(updateDto.ConfigName)) return new(false, "ConfigName cannot be null or empty.");
if (await AppDb.RobotSimulationConfigs.AnyAsync(cf => cf.Id != id && cf.ConfigName == updateDto.ConfigName))
return new(false, "A Simulation configuration with the same name already exists.");
config.EnableSimulation = updateDto.EnableSimulation;
config.SimulationMaxVelocity = updateDto.SimulationMaxVelocity;
config.SimulationMaxAngularVelocity = updateDto.SimulationMaxAngularVelocity;
config.SimulationAcceleration = updateDto.SimulationAcceleration;
config.SimulationDeceleration = updateDto.SimulationDeceleration;
config.ConfigName = updateDto.ConfigName ?? config.ConfigName;
config.Description = updateDto.Description ?? config.Description;
config.UpdatedAt = DateTime.Now;
await AppDb.SaveChangesAsync();
if (config.IsActive) await RobotConfiguration.LoadRobotSimulationConfigAsync();
return new(true, "Simulation configuration updated successfully.")
{
Data = new RobotSimulationConfigDto
{
Id = config.Id,
EnableSimulation = config.EnableSimulation,
SimulationMaxVelocity = config.SimulationMaxVelocity,
SimulationMaxAngularVelocity = config.SimulationMaxAngularVelocity,
SimulationAcceleration = config.SimulationAcceleration,
SimulationDeceleration = config.SimulationDeceleration,
CreatedAt = config.CreatedAt,
UpdatedAt = config.UpdatedAt,
IsActive = config.IsActive,
ConfigName = config.ConfigName,
Description = config.Description
}
};
return new(true, "Simulation configuration updated successfully.");
}
catch (Exception ex)
{
@ -724,6 +653,7 @@ public class RobotConfigsController(Services.Logger<RobotConfigsController> Logg
var config = await AppDb.RobotSimulationConfigs.FindAsync(id);
if (config is null) return new(false, "Simulation configuration not found.");
if (config.IsActive) return new(false, "Cannot delete an active Simulation configuration.");
if (config.ConfigName == "Default") return new(false, "Cannot delete the default PLC configuration.");
AppDb.RobotSimulationConfigs.Remove(config);
await AppDb.SaveChangesAsync();
@ -799,17 +729,13 @@ public class RobotConfigsController(Services.Logger<RobotConfigsController> Logg
[HttpPut]
[Route("safety/{id:guid}")]
public async Task<MessageResult<RobotSafetyConfigDto>> UpdateSafetyConfig(Guid id, [FromBody] UpdateRobotSafetyConfigDto updateDto)
public async Task<MessageResult> UpdateSafetyConfig(Guid id, [FromBody] UpdateRobotSafetyConfigDto updateDto)
{
try
{
var config = await AppDb.RobotSafetyConfigs.FindAsync(id);
if (config is null) return new(false, "Safety configuration not found.");
if (string.IsNullOrEmpty(updateDto.ConfigName)) return new(false, "ConfigName cannot be null or empty.");
if (await AppDb.RobotSafetyConfigs.AnyAsync(cf => cf.Id != id && cf.ConfigName == updateDto.ConfigName))
return new(false, "A Safety configuration with the same name already exists.");
config.SafetySpeedVerySlow = updateDto.SafetySpeedVerySlow;
config.SafetySpeedSlow = updateDto.SafetySpeedSlow;
config.SafetySpeedNormal = updateDto.SafetySpeedNormal;
@ -817,32 +743,13 @@ public class RobotConfigsController(Services.Logger<RobotConfigsController> Logg
config.SafetySpeedOptimal = updateDto.SafetySpeedOptimal;
config.SafetySpeedFast = updateDto.SafetySpeedFast;
config.SafetySpeedVeryFast = updateDto.SafetySpeedVeryFast;
config.ConfigName = updateDto.ConfigName ?? config.ConfigName;
config.Description = updateDto.Description ?? config.Description;
config.UpdatedAt = DateTime.Now;
await AppDb.SaveChangesAsync();
if (config.IsActive) await RobotConfiguration.LoadRobotSafetyConfigAsync();
return new(true, "Safety configuration updated successfully.")
{
Data = new RobotSafetyConfigDto
{
Id = config.Id,
SafetySpeedVerySlow = config.SafetySpeedVerySlow,
SafetySpeedSlow = config.SafetySpeedSlow,
SafetySpeedNormal = config.SafetySpeedNormal,
SafetySpeedMedium = config.SafetySpeedMedium,
SafetySpeedOptimal = config.SafetySpeedOptimal,
SafetySpeedFast = config.SafetySpeedFast,
SafetySpeedVeryFast = config.SafetySpeedVeryFast,
CreatedAt = config.CreatedAt,
UpdatedAt = config.UpdatedAt,
IsActive = config.IsActive,
ConfigName = config.ConfigName,
Description = config.Description
}
};
return new(true, "Safety configuration updated successfully.");
}
catch (Exception ex)
{
@ -916,6 +823,7 @@ public class RobotConfigsController(Services.Logger<RobotConfigsController> Logg
var config = await AppDb.RobotSafetyConfigs.FindAsync(id);
if (config is null) return new(false, "Safety configuration not found.");
if (config.IsActive) return new(false, "Cannot delete an active Safety configuration.");
if (config.ConfigName == "Default") return new(false, "Cannot delete the default PLC configuration.");
AppDb.RobotSafetyConfigs.Remove(config);
await AppDb.SaveChangesAsync();

View File

@ -0,0 +1,577 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using RobotApp.Data;
#nullable disable
namespace RobotApp.Data.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20251031073231_UpdateVDA5050Config")]
partial class UpdateVDA5050Config
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "9.0.9");
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ClaimType")
.HasColumnType("TEXT");
b.Property<string>("ClaimValue")
.HasColumnType("TEXT");
b.Property<string>("RoleId")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AspNetRoleClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ClaimType")
.HasColumnType("TEXT");
b.Property<string>("ClaimValue")
.HasColumnType("TEXT");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AspNetUserClaims", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.Property<string>("LoginProvider")
.HasColumnType("TEXT");
b.Property<string>("ProviderKey")
.HasColumnType("TEXT");
b.Property<string>("ProviderDisplayName")
.HasColumnType("TEXT");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("LoginProvider", "ProviderKey");
b.HasIndex("UserId");
b.ToTable("AspNetUserLogins", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("TEXT");
b.Property<string>("RoleId")
.HasColumnType("TEXT");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId");
b.ToTable("AspNetUserRoles", (string)null);
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.Property<string>("UserId")
.HasColumnType("TEXT");
b.Property<string>("LoginProvider")
.HasColumnType("TEXT");
b.Property<string>("Name")
.HasColumnType("TEXT");
b.Property<string>("Value")
.HasColumnType("TEXT");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AspNetUserTokens", (string)null);
});
modelBuilder.Entity("RobotApp.Data.ApplicationRole", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("TEXT");
b.Property<string>("Name")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<string>("NormalizedName")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("NormalizedName")
.IsUnique()
.HasDatabaseName("RoleNameIndex");
b.ToTable("AspNetRoles", (string)null);
});
modelBuilder.Entity("RobotApp.Data.ApplicationUser", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<int>("AccessFailedCount")
.HasColumnType("INTEGER");
b.Property<string>("ConcurrencyStamp")
.IsConcurrencyToken()
.HasColumnType("TEXT");
b.Property<string>("Email")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<bool>("EmailConfirmed")
.HasColumnType("INTEGER");
b.Property<bool>("LockoutEnabled")
.HasColumnType("INTEGER");
b.Property<DateTimeOffset?>("LockoutEnd")
.HasColumnType("TEXT");
b.Property<string>("NormalizedEmail")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<string>("NormalizedUserName")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.Property<string>("PasswordHash")
.HasColumnType("TEXT");
b.Property<string>("PhoneNumber")
.HasColumnType("TEXT");
b.Property<bool>("PhoneNumberConfirmed")
.HasColumnType("INTEGER");
b.Property<string>("SecurityStamp")
.HasColumnType("TEXT");
b.Property<bool>("TwoFactorEnabled")
.HasColumnType("INTEGER");
b.Property<string>("UserName")
.HasMaxLength(256)
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("NormalizedEmail")
.HasDatabaseName("EmailIndex");
b.HasIndex("NormalizedUserName")
.IsUnique()
.HasDatabaseName("UserNameIndex");
b.ToTable("AspNetUsers", (string)null);
});
modelBuilder.Entity("RobotApp.Data.RobotConfig", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier")
.HasColumnName("Id");
b.Property<string>("ConfigName")
.HasMaxLength(100)
.HasColumnType("nvarchar(64)")
.HasColumnName("ConfigName");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2")
.HasColumnName("CreatedAt");
b.Property<string>("Description")
.HasMaxLength(500)
.HasColumnType("ntext")
.HasColumnName("Description");
b.Property<double>("Height")
.HasColumnType("float")
.HasColumnName("Height");
b.Property<bool>("IsActive")
.HasColumnType("bit")
.HasColumnName("IsActive");
b.Property<double>("Length")
.HasColumnType("float")
.HasColumnName("Length");
b.Property<int>("NavigationType")
.HasColumnType("int")
.HasColumnName("NavigationType");
b.Property<double>("RadiusWheel")
.HasColumnType("float")
.HasColumnName("RadiusWheel");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("datetime2")
.HasColumnName("UpdatedAt");
b.Property<double>("Width")
.HasColumnType("float")
.HasColumnName("Width");
b.HasKey("Id");
b.ToTable("RobotConfig");
});
modelBuilder.Entity("RobotApp.Data.RobotPlcConfig", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier")
.HasColumnName("Id");
b.Property<string>("ConfigName")
.HasMaxLength(100)
.HasColumnType("nvarchar(64)")
.HasColumnName("ConfigName");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2")
.HasColumnName("CreatedAt");
b.Property<string>("Description")
.HasMaxLength(500)
.HasColumnType("ntext")
.HasColumnName("Description");
b.Property<bool>("IsActive")
.HasColumnType("bit")
.HasColumnName("IsActive");
b.Property<string>("PLCAddress")
.HasMaxLength(50)
.HasColumnType("nvarchar(64)")
.HasColumnName("PLCAddress");
b.Property<int>("PLCPort")
.HasColumnType("int")
.HasColumnName("PLCPort");
b.Property<byte>("PLCUnitId")
.HasColumnType("tinyint")
.HasColumnName("PLCUnitId");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("datetime2")
.HasColumnName("UpdatedAt");
b.HasKey("Id");
b.ToTable("RobotPlcConfig");
});
modelBuilder.Entity("RobotApp.Data.RobotSafetyConfig", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier")
.HasColumnName("Id");
b.Property<string>("ConfigName")
.HasMaxLength(100)
.HasColumnType("nvarchar(64)")
.HasColumnName("ConfigName");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2")
.HasColumnName("CreatedAt");
b.Property<string>("Description")
.HasMaxLength(500)
.HasColumnType("ntext")
.HasColumnName("Description");
b.Property<bool>("IsActive")
.HasColumnType("bit")
.HasColumnName("IsActive");
b.Property<double>("SafetySpeedFast")
.HasColumnType("float")
.HasColumnName("SafetySpeedFast");
b.Property<double>("SafetySpeedMedium")
.HasColumnType("float")
.HasColumnName("SafetySpeedMedium");
b.Property<double>("SafetySpeedNormal")
.HasColumnType("float")
.HasColumnName("SafetySpeedNormal");
b.Property<double>("SafetySpeedOptimal")
.HasColumnType("float")
.HasColumnName("SafetySpeedOptimal");
b.Property<double>("SafetySpeedSlow")
.HasColumnType("float")
.HasColumnName("SafetySpeedSlow");
b.Property<double>("SafetySpeedVeryFast")
.HasColumnType("float")
.HasColumnName("SafetySpeedVeryFast");
b.Property<double>("SafetySpeedVerySlow")
.HasColumnType("float")
.HasColumnName("SafetySpeedVerySlow");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("datetime2")
.HasColumnName("UpdatedAt");
b.HasKey("Id");
b.ToTable("RobotSafetyConfig");
});
modelBuilder.Entity("RobotApp.Data.RobotSimulationConfig", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier")
.HasColumnName("Id");
b.Property<string>("ConfigName")
.HasMaxLength(100)
.HasColumnType("nvarchar(64)")
.HasColumnName("ConfigName");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2")
.HasColumnName("CreatedAt");
b.Property<string>("Description")
.HasMaxLength(500)
.HasColumnType("ntext")
.HasColumnName("Description");
b.Property<bool>("EnableSimulation")
.HasColumnType("bit")
.HasColumnName("EnableSimulation");
b.Property<bool>("IsActive")
.HasColumnType("bit")
.HasColumnName("IsActive");
b.Property<double>("SimulationAcceleration")
.HasColumnType("float")
.HasColumnName("SimulationAcceleration");
b.Property<double>("SimulationDeceleration")
.HasColumnType("float")
.HasColumnName("SimulationDeceleration");
b.Property<double>("SimulationMaxAngularVelocity")
.HasColumnType("float")
.HasColumnName("SimulationMaxAngularVelocity");
b.Property<double>("SimulationMaxVelocity")
.HasColumnType("float")
.HasColumnName("SimulationMaxVelocity");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("datetime2")
.HasColumnName("UpdatedAt");
b.HasKey("Id");
b.ToTable("RobotSimulationConfig");
});
modelBuilder.Entity("RobotApp.Data.RobotVDA5050Config", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uniqueidentifier")
.HasColumnName("Id");
b.Property<string>("ConfigName")
.HasMaxLength(100)
.HasColumnType("nvarchar(64)")
.HasColumnName("ConfigName");
b.Property<DateTime>("CreatedAt")
.HasColumnType("datetime2")
.HasColumnName("CreatedAt");
b.Property<string>("Description")
.HasMaxLength(500)
.HasColumnType("ntext")
.HasColumnName("Description");
b.Property<bool>("IsActive")
.HasColumnType("bit")
.HasColumnName("IsActive");
b.Property<string>("SerialNumber")
.HasMaxLength(50)
.HasColumnType("nvarchar(64)")
.HasColumnName("SerialNumber");
b.Property<DateTime>("UpdatedAt")
.HasColumnType("datetime2")
.HasColumnName("UpdatedAt");
b.Property<string>("VDA5050CA")
.HasColumnType("nvarchar(64)")
.HasColumnName("VDA5050_CA");
b.Property<string>("VDA5050Cer")
.HasColumnType("nvarchar(64)")
.HasColumnName("VDA5050_Cer");
b.Property<bool>("VDA5050EnablePassword")
.HasColumnType("bit")
.HasColumnName("VDA5050_EnablePassword");
b.Property<bool>("VDA5050EnableTls")
.HasColumnType("bit")
.HasColumnName("VDA5050_EnableTls");
b.Property<string>("VDA5050HostServer")
.HasMaxLength(100)
.HasColumnType("nvarchar(64)")
.HasColumnName("VDA5050_HostServer");
b.Property<string>("VDA5050Manufacturer")
.HasMaxLength(50)
.HasColumnType("nvarchar(64)")
.HasColumnName("VDA5050_Manufacturer");
b.Property<string>("VDA5050Password")
.HasMaxLength(50)
.HasColumnType("nvarchar(64)")
.HasColumnName("VDA5050_Password");
b.Property<int>("VDA5050Port")
.HasColumnType("int")
.HasColumnName("VDA5050_Port");
b.Property<int>("VDA5050PublishRepeat")
.HasColumnType("int")
.HasColumnName("VDA5050_PublishRepeat");
b.Property<string>("VDA5050UserName")
.HasMaxLength(50)
.HasColumnType("nvarchar(64)")
.HasColumnName("VDA5050_UserName");
b.Property<string>("VDA5050Version")
.HasMaxLength(20)
.HasColumnType("nvarchar(64)")
.HasColumnName("VDA5050_Version");
b.Property<string>("VDA5050_Key")
.HasColumnType("nvarchar(64)")
.HasColumnName("VDA5050_Key");
b.HasKey("Id");
b.ToTable("RobotVDA5050Config");
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
{
b.HasOne("RobotApp.Data.ApplicationRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
{
b.HasOne("RobotApp.Data.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
{
b.HasOne("RobotApp.Data.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
{
b.HasOne("RobotApp.Data.ApplicationRole", null)
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("RobotApp.Data.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
{
b.HasOne("RobotApp.Data.ApplicationUser", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,48 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace RobotApp.Data.Migrations
{
/// <inheritdoc />
public partial class UpdateVDA5050Config : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "VDA5050_CA",
table: "RobotVDA5050Config",
type: "nvarchar(64)",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "VDA5050_Cer",
table: "RobotVDA5050Config",
type: "nvarchar(64)",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "VDA5050_Key",
table: "RobotVDA5050Config",
type: "nvarchar(64)",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "VDA5050_CA",
table: "RobotVDA5050Config");
migrationBuilder.DropColumn(
name: "VDA5050_Cer",
table: "RobotVDA5050Config");
migrationBuilder.DropColumn(
name: "VDA5050_Key",
table: "RobotVDA5050Config");
}
}
}

View File

@ -460,6 +460,14 @@ namespace RobotApp.Migrations
.HasColumnType("datetime2")
.HasColumnName("UpdatedAt");
b.Property<string>("VDA5050CA")
.HasColumnType("nvarchar(64)")
.HasColumnName("VDA5050_CA");
b.Property<string>("VDA5050Cer")
.HasColumnType("nvarchar(64)")
.HasColumnName("VDA5050_Cer");
b.Property<bool>("VDA5050EnablePassword")
.HasColumnType("bit")
.HasColumnName("VDA5050_EnablePassword");
@ -501,6 +509,10 @@ namespace RobotApp.Migrations
.HasColumnType("nvarchar(64)")
.HasColumnName("VDA5050_Version");
b.Property<string>("VDA5050_Key")
.HasColumnType("nvarchar(64)")
.HasColumnName("VDA5050_Key");
b.HasKey("Id");
b.ToTable("RobotVDA5050Config");

View File

@ -51,6 +51,15 @@ public class RobotVDA5050Config
[Column("VDA5050_EnableTls", TypeName = "bit")]
public bool VDA5050EnableTls { get; set; }
[Column("VDA5050_CA", TypeName = "nvarchar(64)")]
public string VDA5050CA { get; set; }
[Column("VDA5050_Cer", TypeName = "nvarchar(64)")]
public string VDA5050Cer { get; set; }
[Column("VDA5050_Key", TypeName = "nvarchar(64)")]
public string VDA5050_Key { get; set; }
[Column("CreatedAt", TypeName = "datetime2")]
public DateTime CreatedAt { get; set; }

Binary file not shown.