diff --git a/RobotApp.Client/Pages/Components/Config/RobotVDA5050Config.razor b/RobotApp.Client/Pages/Components/Config/RobotVDA5050Config.razor index 798210f..3844e42 100644 --- a/RobotApp.Client/Pages/Components/Config/RobotVDA5050Config.razor +++ b/RobotApp.Client/Pages/Components/Config/RobotVDA5050Config.razor @@ -1,63 +1,133 @@ - - - -
-
- - -
-
- - -
-
+@implements IDisposable -
-
- - -
-
- - -
-
- - -
-
+
+ + -
-
- - +
+ + +
-
- - + +
+
+ + + +
+
+ + + +
-
-
- - -
+
+
+ + + +
+
+ + + +
+
+ + + +
+
-
- - -
+
+
+ + + +
+
+ +
+ + +
+ +
+
-
- - -
+
+ + + +
-
- - +
+ + + +
+ +
+ + + @if (!string.IsNullOrEmpty(CaFileName)) + { +
@CaFileName (@FormatSize(CaFileSize))
+ } + @if (!string.IsNullOrEmpty(CaFileError)) + { +
@CaFileError
+ } +
+ +
+ + + @if (!string.IsNullOrEmpty(CertFileName)) + { +
@CertFileName (@FormatSize(CertFileSize))
+ } + @if (!string.IsNullOrEmpty(CertFileError)) + { +
@CertFileError
+ } +
+ +
+ + + @if (!string.IsNullOrEmpty(KeyFileName)) + { +
@KeyFileName (@FormatSize(KeyFileSize))
+ } + @if (!string.IsNullOrEmpty(KeyFileError)) + { +
@KeyFileError
+ } +
+ +
+ + + +
+ +
+
+ @if (Model.CreatedAt != default || Model.UpdatedAt != default) + { +
+ Created: @Model.CreatedAt.ToString("dd/MM/yyyy HH:mm:ss") + Updated: @Model.UpdatedAt.ToString("dd/MM/yyyy HH:mm:ss") +
+ }
- +
+ @code { [Parameter] @@ -66,16 +136,136 @@ [Parameter] public EventCallback 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; } } diff --git a/RobotApp.Client/Pages/Components/Config/RobotVDA5050Config.razor.css b/RobotApp.Client/Pages/Components/Config/RobotVDA5050Config.razor.css new file mode 100644 index 0000000..a0c2c3c --- /dev/null +++ b/RobotApp.Client/Pages/Components/Config/RobotVDA5050Config.razor.css @@ -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 */ +} \ No newline at end of file diff --git a/RobotApp.Client/Pages/RobotConfigManager.razor b/RobotApp.Client/Pages/RobotConfigManager.razor index 5b09ce1..aa54d06 100644 --- a/RobotApp.Client/Pages/RobotConfigManager.razor +++ b/RobotApp.Client/Pages/RobotConfigManager.razor @@ -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 Robot Configuration -
+
- @foreach (var type in Enum.GetValues()) { @@ -21,7 +23,7 @@
@@ -29,15 +31,15 @@
- - -
@@ -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:
No configs.
@@ -100,14 +102,93 @@
+ + @if (IsLoading) + { +
+
+ +
Loading…
+
+
+ } + + @if (IsAddingNew) + { + + } + + @if (ShowDeleteConfirm) + { + + } +
@code { - private List VdaConfigs { get; } = []; - private List SafetyConfigs { get; } = []; - private List SimulationConfigs { get; } = []; - private List PlcConfigs { get; } = []; - private List CoreConfigs { get; } = []; + private List VdaConfigs { get; set; } = new(); + private List SafetyConfigs { get; set; } = new(); + private List SimulationConfigs { get; set; } = new(); + private List PlcConfigs { get; set; } = new(); + private List 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(List list, Func> 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(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(List list, Action onSelect, Func nameSelector, Func? isActiveSelector = null) where T : class { return builder => { @@ -141,31 +305,47 @@ { 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(); } - builder.CloseElement(); + builder.CloseElement(); } - builder.CloseElement(); + 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; + } } diff --git a/RobotApp.Client/Pages/RobotConfigManager.razor.cs b/RobotApp.Client/Pages/RobotConfigManager.razor.cs new file mode 100644 index 0000000..812716c --- /dev/null +++ b/RobotApp.Client/Pages/RobotConfigManager.razor.cs @@ -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>("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>("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>("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>("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>("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(); + 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(); + 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(); + 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(); + 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(); + 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(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(); + } + } +} diff --git a/RobotApp.Client/Pages/RobotConfigManager.razor.css b/RobotApp.Client/Pages/RobotConfigManager.razor.css index 315ca52..cb7c621 100644 --- a/RobotApp.Client/Pages/RobotConfigManager.razor.css +++ b/RobotApp.Client/Pages/RobotConfigManager.razor.css @@ -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; + } \ No newline at end of file diff --git a/RobotApp.Common.Shares/Dtos/RobotConfigDto.cs b/RobotApp.Common.Shares/Dtos/RobotConfigDto.cs index 613e441..0b0acdf 100644 --- a/RobotApp.Common.Shares/Dtos/RobotConfigDto.cs +++ b/RobotApp.Common.Shares/Dtos/RobotConfigDto.cs @@ -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; } } diff --git a/RobotApp.Common.Shares/Dtos/RobotPlcConfigDto.cs b/RobotApp.Common.Shares/Dtos/RobotPlcConfigDto.cs index 58d8dc1..ec6e03a 100644 --- a/RobotApp.Common.Shares/Dtos/RobotPlcConfigDto.cs +++ b/RobotApp.Common.Shares/Dtos/RobotPlcConfigDto.cs @@ -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; } diff --git a/RobotApp.Common.Shares/Dtos/RobotSafetyConfigDto.cs b/RobotApp.Common.Shares/Dtos/RobotSafetyConfigDto.cs index 2c7d8ef..9fc964a 100644 --- a/RobotApp.Common.Shares/Dtos/RobotSafetyConfigDto.cs +++ b/RobotApp.Common.Shares/Dtos/RobotSafetyConfigDto.cs @@ -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; } } diff --git a/RobotApp.Common.Shares/Dtos/RobotSimulationConfigDto.cs b/RobotApp.Common.Shares/Dtos/RobotSimulationConfigDto.cs index 68ab802..5746876 100644 --- a/RobotApp.Common.Shares/Dtos/RobotSimulationConfigDto.cs +++ b/RobotApp.Common.Shares/Dtos/RobotSimulationConfigDto.cs @@ -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; } } diff --git a/RobotApp.Common.Shares/Dtos/RobotVDA5050ConfigDto.cs b/RobotApp.Common.Shares/Dtos/RobotVDA5050ConfigDto.cs index 04f1b02..faa6cce 100644 --- a/RobotApp.Common.Shares/Dtos/RobotVDA5050ConfigDto.cs +++ b/RobotApp.Common.Shares/Dtos/RobotVDA5050ConfigDto.cs @@ -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; } } diff --git a/RobotApp/Controllers/RobotConfigsController.cs b/RobotApp/Controllers/RobotConfigsController.cs index 9bd7fc9..e7277aa 100644 --- a/RobotApp/Controllers/RobotConfigsController.cs +++ b/RobotApp/Controllers/RobotConfigsController.cs @@ -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 Logg [HttpPut] [Route("plc/{id:guid}")] - public async Task> UpdatePLCConfig(Guid id, [FromBody] UpdateRobotPlcConfigDto updateDto) + public async Task 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 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 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 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 Logg [HttpPut] [Route("vda5050/{id:guid}")] - public async Task> UpdateVDA5050Config(Guid id, [FromBody] UpdateRobotVDA5050ConfigDto updateDto) + public async Task 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 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 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 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 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 Logg [HttpPut] [Route("robot/{id:guid}")] - public async Task> UpdateRobotConfig(Guid id, [FromBody] UpdateRobotConfigDto updateDto) + public async Task 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 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 Logg [HttpPut] [Route("simulation/{id:guid}")] - public async Task> UpdateSimulationConfig(Guid id, [FromBody] UpdateRobotSimulationConfigDto updateDto) + public async Task 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 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 Logg [HttpPut] [Route("safety/{id:guid}")] - public async Task> UpdateSafetyConfig(Guid id, [FromBody] UpdateRobotSafetyConfigDto updateDto) + public async Task 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 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 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(); diff --git a/RobotApp/Data/Migrations/20251031073231_UpdateVDA5050Config.Designer.cs b/RobotApp/Data/Migrations/20251031073231_UpdateVDA5050Config.Designer.cs new file mode 100644 index 0000000..8bda907 --- /dev/null +++ b/RobotApp/Data/Migrations/20251031073231_UpdateVDA5050Config.Designer.cs @@ -0,0 +1,577 @@ +// +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 + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "9.0.9"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("RobotApp.Data.ApplicationRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("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("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier") + .HasColumnName("Id"); + + b.Property("ConfigName") + .HasMaxLength(100) + .HasColumnType("nvarchar(64)") + .HasColumnName("ConfigName"); + + b.Property("CreatedAt") + .HasColumnType("datetime2") + .HasColumnName("CreatedAt"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("ntext") + .HasColumnName("Description"); + + b.Property("Height") + .HasColumnType("float") + .HasColumnName("Height"); + + b.Property("IsActive") + .HasColumnType("bit") + .HasColumnName("IsActive"); + + b.Property("Length") + .HasColumnType("float") + .HasColumnName("Length"); + + b.Property("NavigationType") + .HasColumnType("int") + .HasColumnName("NavigationType"); + + b.Property("RadiusWheel") + .HasColumnType("float") + .HasColumnName("RadiusWheel"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2") + .HasColumnName("UpdatedAt"); + + b.Property("Width") + .HasColumnType("float") + .HasColumnName("Width"); + + b.HasKey("Id"); + + b.ToTable("RobotConfig"); + }); + + modelBuilder.Entity("RobotApp.Data.RobotPlcConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier") + .HasColumnName("Id"); + + b.Property("ConfigName") + .HasMaxLength(100) + .HasColumnType("nvarchar(64)") + .HasColumnName("ConfigName"); + + b.Property("CreatedAt") + .HasColumnType("datetime2") + .HasColumnName("CreatedAt"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("ntext") + .HasColumnName("Description"); + + b.Property("IsActive") + .HasColumnType("bit") + .HasColumnName("IsActive"); + + b.Property("PLCAddress") + .HasMaxLength(50) + .HasColumnType("nvarchar(64)") + .HasColumnName("PLCAddress"); + + b.Property("PLCPort") + .HasColumnType("int") + .HasColumnName("PLCPort"); + + b.Property("PLCUnitId") + .HasColumnType("tinyint") + .HasColumnName("PLCUnitId"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2") + .HasColumnName("UpdatedAt"); + + b.HasKey("Id"); + + b.ToTable("RobotPlcConfig"); + }); + + modelBuilder.Entity("RobotApp.Data.RobotSafetyConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier") + .HasColumnName("Id"); + + b.Property("ConfigName") + .HasMaxLength(100) + .HasColumnType("nvarchar(64)") + .HasColumnName("ConfigName"); + + b.Property("CreatedAt") + .HasColumnType("datetime2") + .HasColumnName("CreatedAt"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("ntext") + .HasColumnName("Description"); + + b.Property("IsActive") + .HasColumnType("bit") + .HasColumnName("IsActive"); + + b.Property("SafetySpeedFast") + .HasColumnType("float") + .HasColumnName("SafetySpeedFast"); + + b.Property("SafetySpeedMedium") + .HasColumnType("float") + .HasColumnName("SafetySpeedMedium"); + + b.Property("SafetySpeedNormal") + .HasColumnType("float") + .HasColumnName("SafetySpeedNormal"); + + b.Property("SafetySpeedOptimal") + .HasColumnType("float") + .HasColumnName("SafetySpeedOptimal"); + + b.Property("SafetySpeedSlow") + .HasColumnType("float") + .HasColumnName("SafetySpeedSlow"); + + b.Property("SafetySpeedVeryFast") + .HasColumnType("float") + .HasColumnName("SafetySpeedVeryFast"); + + b.Property("SafetySpeedVerySlow") + .HasColumnType("float") + .HasColumnName("SafetySpeedVerySlow"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2") + .HasColumnName("UpdatedAt"); + + b.HasKey("Id"); + + b.ToTable("RobotSafetyConfig"); + }); + + modelBuilder.Entity("RobotApp.Data.RobotSimulationConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier") + .HasColumnName("Id"); + + b.Property("ConfigName") + .HasMaxLength(100) + .HasColumnType("nvarchar(64)") + .HasColumnName("ConfigName"); + + b.Property("CreatedAt") + .HasColumnType("datetime2") + .HasColumnName("CreatedAt"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("ntext") + .HasColumnName("Description"); + + b.Property("EnableSimulation") + .HasColumnType("bit") + .HasColumnName("EnableSimulation"); + + b.Property("IsActive") + .HasColumnType("bit") + .HasColumnName("IsActive"); + + b.Property("SimulationAcceleration") + .HasColumnType("float") + .HasColumnName("SimulationAcceleration"); + + b.Property("SimulationDeceleration") + .HasColumnType("float") + .HasColumnName("SimulationDeceleration"); + + b.Property("SimulationMaxAngularVelocity") + .HasColumnType("float") + .HasColumnName("SimulationMaxAngularVelocity"); + + b.Property("SimulationMaxVelocity") + .HasColumnType("float") + .HasColumnName("SimulationMaxVelocity"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2") + .HasColumnName("UpdatedAt"); + + b.HasKey("Id"); + + b.ToTable("RobotSimulationConfig"); + }); + + modelBuilder.Entity("RobotApp.Data.RobotVDA5050Config", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier") + .HasColumnName("Id"); + + b.Property("ConfigName") + .HasMaxLength(100) + .HasColumnType("nvarchar(64)") + .HasColumnName("ConfigName"); + + b.Property("CreatedAt") + .HasColumnType("datetime2") + .HasColumnName("CreatedAt"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("ntext") + .HasColumnName("Description"); + + b.Property("IsActive") + .HasColumnType("bit") + .HasColumnName("IsActive"); + + b.Property("SerialNumber") + .HasMaxLength(50) + .HasColumnType("nvarchar(64)") + .HasColumnName("SerialNumber"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2") + .HasColumnName("UpdatedAt"); + + b.Property("VDA5050CA") + .HasColumnType("nvarchar(64)") + .HasColumnName("VDA5050_CA"); + + b.Property("VDA5050Cer") + .HasColumnType("nvarchar(64)") + .HasColumnName("VDA5050_Cer"); + + b.Property("VDA5050EnablePassword") + .HasColumnType("bit") + .HasColumnName("VDA5050_EnablePassword"); + + b.Property("VDA5050EnableTls") + .HasColumnType("bit") + .HasColumnName("VDA5050_EnableTls"); + + b.Property("VDA5050HostServer") + .HasMaxLength(100) + .HasColumnType("nvarchar(64)") + .HasColumnName("VDA5050_HostServer"); + + b.Property("VDA5050Manufacturer") + .HasMaxLength(50) + .HasColumnType("nvarchar(64)") + .HasColumnName("VDA5050_Manufacturer"); + + b.Property("VDA5050Password") + .HasMaxLength(50) + .HasColumnType("nvarchar(64)") + .HasColumnName("VDA5050_Password"); + + b.Property("VDA5050Port") + .HasColumnType("int") + .HasColumnName("VDA5050_Port"); + + b.Property("VDA5050PublishRepeat") + .HasColumnType("int") + .HasColumnName("VDA5050_PublishRepeat"); + + b.Property("VDA5050UserName") + .HasMaxLength(50) + .HasColumnType("nvarchar(64)") + .HasColumnName("VDA5050_UserName"); + + b.Property("VDA5050Version") + .HasMaxLength(20) + .HasColumnType("nvarchar(64)") + .HasColumnName("VDA5050_Version"); + + b.Property("VDA5050_Key") + .HasColumnType("nvarchar(64)") + .HasColumnName("VDA5050_Key"); + + b.HasKey("Id"); + + b.ToTable("RobotVDA5050Config"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("RobotApp.Data.ApplicationRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("RobotApp.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("RobotApp.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", 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", b => + { + b.HasOne("RobotApp.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/RobotApp/Data/Migrations/20251031073231_UpdateVDA5050Config.cs b/RobotApp/Data/Migrations/20251031073231_UpdateVDA5050Config.cs new file mode 100644 index 0000000..b525df1 --- /dev/null +++ b/RobotApp/Data/Migrations/20251031073231_UpdateVDA5050Config.cs @@ -0,0 +1,48 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace RobotApp.Data.Migrations +{ + /// + public partial class UpdateVDA5050Config : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "VDA5050_CA", + table: "RobotVDA5050Config", + type: "nvarchar(64)", + nullable: true); + + migrationBuilder.AddColumn( + name: "VDA5050_Cer", + table: "RobotVDA5050Config", + type: "nvarchar(64)", + nullable: true); + + migrationBuilder.AddColumn( + name: "VDA5050_Key", + table: "RobotVDA5050Config", + type: "nvarchar(64)", + nullable: true); + } + + /// + 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"); + } + } +} diff --git a/RobotApp/Data/Migrations/ApplicationDbContextModelSnapshot.cs b/RobotApp/Data/Migrations/ApplicationDbContextModelSnapshot.cs index 2a02fed..d4f4b21 100644 --- a/RobotApp/Data/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/RobotApp/Data/Migrations/ApplicationDbContextModelSnapshot.cs @@ -460,6 +460,14 @@ namespace RobotApp.Migrations .HasColumnType("datetime2") .HasColumnName("UpdatedAt"); + b.Property("VDA5050CA") + .HasColumnType("nvarchar(64)") + .HasColumnName("VDA5050_CA"); + + b.Property("VDA5050Cer") + .HasColumnType("nvarchar(64)") + .HasColumnName("VDA5050_Cer"); + b.Property("VDA5050EnablePassword") .HasColumnType("bit") .HasColumnName("VDA5050_EnablePassword"); @@ -501,6 +509,10 @@ namespace RobotApp.Migrations .HasColumnType("nvarchar(64)") .HasColumnName("VDA5050_Version"); + b.Property("VDA5050_Key") + .HasColumnType("nvarchar(64)") + .HasColumnName("VDA5050_Key"); + b.HasKey("Id"); b.ToTable("RobotVDA5050Config"); diff --git a/RobotApp/Data/RobotVDA5050Config.cs b/RobotApp/Data/RobotVDA5050Config.cs index ded7cdd..1cd0f5f 100644 --- a/RobotApp/Data/RobotVDA5050Config.cs +++ b/RobotApp/Data/RobotVDA5050Config.cs @@ -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; } diff --git a/RobotApp/robot.db b/RobotApp/robot.db index f1b47f6..a181009 100644 Binary files a/RobotApp/robot.db and b/RobotApp/robot.db differ