272 lines
11 KiB
Plaintext
272 lines
11 KiB
Plaintext
@implements IDisposable
|
|
|
|
<div class="d-flex w-100 h-100 flex-column">
|
|
<EditForm EditContext="EditContext">
|
|
<DataAnnotationsValidator />
|
|
|
|
<div class="mb-2">
|
|
<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 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 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" for="description">Description</label>
|
|
<InputTextArea id="description m-1" class="form-control" @bind-Value="Model.Description" />
|
|
<ValidationMessage For="@(() => Model.Description)" />
|
|
</div>
|
|
</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]
|
|
public RobotVDA5050ConfigDto Model { get; set; } = new();
|
|
|
|
[Parameter]
|
|
public EventCallback<RobotVDA5050ConfigDto> ModelChanged { get; set; }
|
|
|
|
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()
|
|
{
|
|
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 void TogglePasswordVisibility()
|
|
{
|
|
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;
|
|
}
|
|
}
|