RobotApp/RobotApp.Client/Pages/Components/Config/RobotVDA5050Config.razor
Đăng Nguyễn aea55d52f1 update
2025-10-31 15:03:37 +07:00

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;
}
}