437 lines
18 KiB
Plaintext
437 lines
18 KiB
Plaintext
@page "/robot-config"
|
|
@rendermode InteractiveWebAssemblyNoPrerender
|
|
@attribute [Authorize]
|
|
|
|
@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 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" value="@SelectedType" @onchange="OnTypeChanged">
|
|
@foreach (var type in Enum.GetValues<RobotConfigType>())
|
|
{
|
|
<option value="@type">@type</option>
|
|
}
|
|
</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" />
|
|
</svg>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<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" @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" @onclick="SaveConfig">
|
|
<i class="mdi mdi-content-save" aria-hidden="true"></i>
|
|
</button>
|
|
|
|
<button type="button" class="btn rcm-icon-btn" data-tooltip="Load config" aria-label="Update" @onclick="LoadConfig">
|
|
<i class="mdi mdi-file-download" aria-hidden="true"></i>
|
|
</button>
|
|
|
|
<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>
|
|
</div>
|
|
</div>
|
|
<div class="content rcm-content flex-grow-1 d-flex gap-3 mt-3">
|
|
<div class="card p-2 config-list rcm-config-list">
|
|
<div class="card-body list-body p-0">
|
|
@switch (SelectedType)
|
|
{
|
|
case RobotConfigType.VDA5050:
|
|
@RenderList(VdaConfigs, SelectVda, v => v.ConfigName, v => v.IsActive)
|
|
break;
|
|
case RobotConfigType.Safety:
|
|
@RenderList(SafetyConfigs, SelectSafety, s => s.ConfigName, s => s.IsActive)
|
|
break;
|
|
case RobotConfigType.Simulation:
|
|
@RenderList(SimulationConfigs, SelectSimulation, s => s.ConfigName, s => s.IsActive)
|
|
break;
|
|
case RobotConfigType.PLC:
|
|
@RenderList(PlcConfigs, SelectPlc, p => p.ConfigName, p => p.IsActive)
|
|
break;
|
|
case RobotConfigType.Core:
|
|
@RenderList(CoreConfigs, SelectCore, c => c.ConfigName, c => c.IsActive)
|
|
break;
|
|
default:
|
|
<div class="p-2">No configs.</div>
|
|
break;
|
|
}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card p-2 config-content rcm-config-content">
|
|
<div class="card-body">
|
|
@if (!HasSelection)
|
|
{
|
|
<div class="text-muted">Select a config from the list or click Add to create one.</div>
|
|
}
|
|
else
|
|
{
|
|
@switch (SelectedType)
|
|
{
|
|
case RobotConfigType.VDA5050:
|
|
<RobotApp.Client.Pages.Components.Config.RobotVDA5050Config @ref="@RobotVDA5050ConfigRef" @bind-Model="SelectedVda" />
|
|
break;
|
|
case RobotConfigType.Safety:
|
|
<RobotApp.Client.Pages.Components.Config.RobotSafetyConfig @bind-Model="SelectedSafety" />
|
|
break;
|
|
case RobotConfigType.Simulation:
|
|
<RobotApp.Client.Pages.Components.Config.RobotSimulationConfig @bind-Model="SelectedSimulation" />
|
|
break;
|
|
case RobotConfigType.PLC:
|
|
<RobotApp.Client.Pages.Components.Config.RobotPLCConfig @bind-Model="SelectedPlc" />
|
|
break;
|
|
case RobotConfigType.Core:
|
|
<RobotApp.Client.Pages.Components.Config.RobotConfig @bind-Model="SelectedCore" />
|
|
break;
|
|
}
|
|
}
|
|
</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; 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; }
|
|
private RobotSimulationConfigDto? SelectedSimulation { get; set; }
|
|
private RobotPlcConfigDto? SelectedPlc { get; set; }
|
|
private RobotConfigDto? SelectedCore { get; set; }
|
|
|
|
private CreateRobotVDA5050ConfigDto CreateVda = new();
|
|
private CreateRobotConfigDto CreateSafety = new();
|
|
private CreateRobotSafetyConfigDto CreateSimulation = new();
|
|
private CreateRobotSimulationConfigDto CreatePlc = new();
|
|
private CreateRobotPlcConfigDto CreateCore = new();
|
|
|
|
private RobotConfigType SelectedType = RobotConfigType.VDA5050;
|
|
|
|
private RobotApp.Client.Pages.Components.Config.RobotVDA5050Config RobotVDA5050ConfigRef = default!;
|
|
|
|
private int SelectedIndex = -1;
|
|
private bool HasSelection => SelectedIndex >= 0;
|
|
private bool IsLoading = false;
|
|
private bool IsAddingNew = false;
|
|
|
|
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 =>
|
|
{
|
|
if (list is null || !list.Any())
|
|
{
|
|
builder.OpenElement(0, "div");
|
|
builder.AddAttribute(1, "class", "p-2 text-muted");
|
|
builder.AddContent(2, "No configs found.");
|
|
builder.CloseElement();
|
|
return;
|
|
}
|
|
|
|
builder.OpenElement(3, "ul");
|
|
builder.AddAttribute(4, "class", "list-group list-group-flush");
|
|
|
|
for (int i = 0; i < list.Count; i++)
|
|
{
|
|
var item = list[i];
|
|
var idx = i;
|
|
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)));
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
if (isActive)
|
|
{
|
|
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();
|
|
};
|
|
}
|
|
|
|
private Action<int> SelectVda => idx =>
|
|
{
|
|
SelectedIndex = idx;
|
|
SelectedVda = idx >= 0 && idx < VdaConfigs.Count ? VdaConfigs[idx] with { } : null;
|
|
StateHasChanged();
|
|
};
|
|
|
|
private Action<int> SelectSafety => idx =>
|
|
{
|
|
SelectedIndex = idx;
|
|
SelectedSafety = idx >= 0 && idx < SafetyConfigs.Count ? SafetyConfigs[idx] with { } : null;
|
|
StateHasChanged();
|
|
};
|
|
|
|
private Action<int> SelectSimulation => idx =>
|
|
{
|
|
SelectedIndex = idx;
|
|
SelectedSimulation = idx >= 0 && idx < SimulationConfigs.Count ? SimulationConfigs[idx] with { } : null;
|
|
StateHasChanged();
|
|
};
|
|
|
|
private Action<int> SelectPlc => idx =>
|
|
{
|
|
SelectedIndex = idx;
|
|
SelectedPlc = idx >= 0 && idx < PlcConfigs.Count ? PlcConfigs[idx] with { } : null;
|
|
StateHasChanged();
|
|
};
|
|
|
|
private Action<int> SelectCore => idx =>
|
|
{
|
|
SelectedIndex = idx;
|
|
SelectedCore = idx >= 0 && idx < CoreConfigs.Count ? CoreConfigs[idx] with { } : null;
|
|
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;
|
|
}
|
|
}
|