Files
I150/srcs/RobotNet10/Components/RobotNet10.CustomConfigurationEditor/README.md
2026-07-03 16:37:12 +07:00

563 lines
14 KiB
Markdown

# RobotNet10.CustomConfigurationEditor
Component Blazor để quản lý cấu hình với giao diện người dùng, tích hợp với `RobotNet10.CustomConfiguration` backend.
## 📋 Mục lục
- [Tính năng](#tính-năng)
- [Cài đặt](#cài-đặt)
- [Sử dụng Component](#sử-dụng-component)
- [Sử dụng Services](#sử-dụng-services)
- [Components](#components)
- [Models](#models)
- [Ví dụ](#ví-dụ)
## ✨ Tính năng
- 🎨 **Giao diện đẹp**: Sử dụng MudBlazor components
- 📋 **Danh sách Configs**: Hiển thị và tìm kiếm configs
- ✏️ **Editor**: Chỉnh sửa config và variables
- 📤 **Import/Export**: Import và export config files
- 🔍 **Tìm kiếm**: Tìm kiếm configs theo tên hoặc ConfigType
-**Validation**: Validation real-time khi chỉnh sửa
- 🎯 **Type-aware Editor**: Editor tự động thay đổi theo type của variable
## 🚀 Cài đặt
### 1. Thêm Project Reference
```xml
<ItemGroup>
<ProjectReference Include="..\Components\RobotNet10.CustomConfigurationEditor\RobotNet10.CustomConfigurationEditor.csproj" />
</ItemGroup>
```
### 2. Đăng ký Services
Trong `Program.cs` hoặc `Client/Program.cs`:
```csharp
using RobotNet10.CustomConfigurationEditor.Services.API;
using RobotNet10.CustomConfigurationEditor.Services.State;
// HttpClient (nếu chưa có)
builder.Services.AddScoped(sp => new HttpClient
{
BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)
});
// MudBlazor (nếu chưa có)
builder.Services.AddMudServices();
// CustomConfiguration Services
builder.Services.AddScoped<ConfigApiService>();
builder.Services.AddScoped<ConfigManagerState>();
```
### 3. Copy JavaScript File
Copy `wwwroot/js/downloadFile.js` vào `wwwroot/js/` của project frontend và thêm vào `index.html`:
```html
<script src="js/downloadFile.js"></script>
```
### 4. Thêm Using
Trong `_Imports.razor`:
```razor
@using RobotNet10.CustomConfigurationEditor.Components.ConfigManager
@using RobotNet10.CustomConfigurationEditor.Services.API
@using RobotNet10.CustomConfigurationEditor.Services.State
```
## 📖 Sử dụng Component
### Cách 1: Sử dụng Component trực tiếp
Tạo page mới:
```razor
@page "/config-manager"
@using RobotNet10.CustomConfigurationEditor.Components.ConfigManager
<PageTitle>Configuration Manager</PageTitle>
<MudContainer MaxWidth="MaxWidth.ExtraLarge" Class="mt-4">
<ConfigManagerComponent />
</MudContainer>
```
### Cách 2: Sử dụng trong Layout
Thêm vào navigation menu:
```razor
<MudNavLink Href="/config-manager" Match="NavLinkMatch.All">
<MudIcon Icon="@Icons.Material.Filled.Settings" />
Configuration Manager
</MudNavLink>
```
## 🔧 Sử dụng Services
### ConfigApiService
Service để gọi API backend:
```csharp
@inject ConfigApiService ApiService
@code {
protected override async Task OnInitializedAsync()
{
// Lấy tất cả configs
var configs = await ApiService.GetAllConfigsAsync();
// Lấy config theo ID
var config = await ApiService.GetConfigByIdAsync(id);
// Lấy config theo ConfigType
var mqttConfig = await ApiService.GetConfigByTypeAsync("MQTTBrokerConfig");
// Tạo config mới
var newConfig = await ApiService.CreateConfigAsync(
configType: "MyConfig",
variables: variables,
description: "My config"
);
// Cập nhật config
await ApiService.UpdateConfigAsync(id, variables, description);
// Xóa config
await ApiService.DeleteConfigAsync(id);
// Import config
using var stream = file.OpenReadStream();
var imported = await ApiService.ImportConfigAsync(stream, file.Name, "MyConfig");
// Export config
var exportStream = await ApiService.ExportConfigAsync(id);
// Cập nhật variable
await ApiService.UpdateVariableAsync(id, "port", 8080);
// Thêm variable
await ApiService.AddVariableAsync(id, newVariable);
// Xóa variable
await ApiService.RemoveVariableAsync(id, "variableName");
}
}
```
### ConfigManagerState
State management service với events:
```csharp
@inject ConfigManagerState State
<MudButton OnClick="LoadConfigs">Load Configs</MudButton>
@if (State.IsLoading)
{
<MudProgressLinear />
}
@if (!string.IsNullOrEmpty(State.ErrorMessage))
{
<MudAlert Severity="Severity.Error">@State.ErrorMessage</MudAlert>
}
@code {
protected override async Task OnInitializedAsync()
{
// Subscribe to state changes
State.OnStateChanged += StateChanged;
// Load configs
await State.LoadConfigsAsync();
}
private async Task LoadConfigs()
{
await State.LoadConfigsAsync();
}
private void StateChanged()
{
StateHasChanged();
}
public void Dispose()
{
State.OnStateChanged -= StateChanged;
}
}
```
## 🧩 Components
### ConfigManagerComponent
Component chính để quản lý configs.
**Sử dụng:**
```razor
<ConfigManagerComponent />
```
**Tính năng:**
- Toolbar với search, import, create buttons
- Split view: List panel và Editor panel
- Error handling và notifications
### ConfigListPanel
Panel hiển thị danh sách configs.
**Parameters:**
- `State`: ConfigManagerState
- `OnConfigSelected`: EventCallback khi chọn config
**Sử dụng:**
```razor
<ConfigListPanel
State="@State"
OnConfigSelected="@(EventCallback.Factory.Create<ConfigFileMetadataModel>(this, OnConfigSelected))" />
```
### ConfigEditorPanel
Panel để chỉnh sửa config.
**Parameters:**
- `State`: ConfigManagerState
- `OnSave`: EventCallback khi save
- `OnDelete`: EventCallback khi delete
- `OnExport`: EventCallback khi export
**Sử dụng:**
```razor
<ConfigEditorPanel
State="@State"
OnSave="@(EventCallback.Factory.Create(this, OnSave))"
OnDelete="@(EventCallback.Factory.Create<Guid>(this, OnDelete))"
OnExport="@(EventCallback.Factory.Create<Guid>(this, OnExport))" />
```
### VariableEditor
Component để chỉnh sửa một variable, tự động thay đổi input type theo variable type.
**Parameters:**
- `Variable`: ConfigVariableModel
- `OnValueChanged`: EventCallback khi value thay đổi
**Sử dụng:**
```razor
<VariableEditor
Variable="@variable"
OnValueChanged="@(EventCallback.Factory.Create<object?>(this, OnValueChanged))" />
```
**Hỗ trợ types:**
- `string`: MudTextField
- `int`: MudNumericField với Min/Max
- `double`: MudNumericField với Min/Max
- `bool`: MudSwitch
- `enum`: MudSelect với EnumValues
- `object`: MudTextField multiline (JSON)
- `array`: MudTextField multiline (JSON)
### Dialogs
#### ImportConfigDialog
Dialog để import config từ file.
```razor
var dialog = await DialogService.ShowAsync<ImportConfigDialog>("Import Config");
var result = await dialog.Result;
```
#### ExportConfigDialog
Dialog để export config.
```razor
var dialog = await DialogService.ShowAsync<ExportConfigDialog>("Export Config");
```
#### CreateConfigDialog
Dialog để tạo config mới.
```razor
var dialog = await DialogService.ShowAsync<CreateConfigDialog>("Create Config");
```
#### EditConfigDialog
Dialog để chỉnh sửa metadata của config.
```razor
var dialog = await DialogService.ShowAsync<EditConfigDialog>("Edit Config");
```
## 📦 Models
### ConfigFileModel
```csharp
public class ConfigFileModel
{
public Guid Id { get; set; }
public string ConfigType { get; set; } = string.Empty;
public List<ConfigVariableModel> Variables { get; set; } = new();
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
public string? Description { get; set; }
}
```
### ConfigFileMetadataModel
```csharp
public class ConfigFileMetadataModel
{
public Guid Id { get; set; }
public string ConfigType { get; set; } = string.Empty;
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
public string? Description { get; set; }
}
```
### ConfigVariableModel
```csharp
public class ConfigVariableModel
{
public string Name { get; set; } = string.Empty;
public string Type { get; set; } = string.Empty; // "string", "int", "double", "bool", "enum", "object", "array"
public object? Value { get; set; }
public double? Min { get; set; }
public double? Max { get; set; }
public string? Roles { get; set; }
public List<string>? EnumValues { get; set; }
}
```
## 💡 Ví dụ
### Ví dụ 1: Custom Page với State
```razor
@page "/my-configs"
@inject ConfigManagerState State
@inject ISnackbar Snackbar
<PageTitle>My Configs</PageTitle>
<MudContainer>
<MudText Typo="Typo.h4" Class="mb-4">My Configurations</MudText>
@if (State.IsLoading)
{
<MudProgressLinear />
}
<MudGrid>
@foreach (var config in State.Configs)
{
<MudItem xs="12" md="6" lg="4">
<MudCard>
<MudCardContent>
<MudText Typo="Typo.h6">@config.ConfigType</MudText>
<MudText Typo="Typo.body2">@config.Description</MudText>
</MudCardContent>
<MudCardActions>
<MudButton OnClick="@(() => SelectConfig(config))">Select</MudButton>
</MudCardActions>
</MudCard>
</MudItem>
}
</MudGrid>
</MudContainer>
@code {
protected override async Task OnInitializedAsync()
{
State.OnStateChanged += StateChanged;
await State.LoadConfigsAsync();
}
private async Task SelectConfig(ConfigFileMetadataModel config)
{
await State.SelectConfigAsync(config);
Snackbar.Add($"Selected {config.ConfigType}", Severity.Success);
}
private void StateChanged()
{
StateHasChanged();
}
public void Dispose()
{
State.OnStateChanged -= StateChanged;
}
}
```
### Ví dụ 2: Custom Variable Editor
```razor
@inject ConfigManagerState State
<MudTable Items="@State.SelectedConfig?.Variables" Hover="true">
<HeaderContent>
<MudTh>Name</MudTh>
<MudTh>Type</MudTh>
<MudTh>Value</MudTh>
<MudTh>Actions</MudTh>
</HeaderContent>
<RowTemplate>
<MudTd>@context.Name</MudTd>
<MudTd>@context.Type</MudTd>
<MudTd>
<VariableEditor
Variable="@context"
OnValueChanged="@(EventCallback.Factory.Create<object?>(this, value => OnVariableChanged(context.Name, value)))" />
</MudTd>
<MudTd>
<MudIconButton Icon="@Icons.Material.Filled.Delete"
OnClick="@(() => DeleteVariable(context.Name))" />
</MudTd>
</RowTemplate>
</MudTable>
@code {
private async Task OnVariableChanged(string name, object? value)
{
await State.UpdateVariableAsync(name, value);
}
private async Task DeleteVariable(string name)
{
await State.RemoveVariableAsync(name);
}
}
```
### Ví dụ 3: Import Config với Custom Logic
```razor
@inject IDialogService DialogService
@inject ConfigManagerState State
@inject ISnackbar Snackbar
<MudButton OnClick="ImportConfig">Import Config</MudButton>
@code {
private async Task ImportConfig()
{
var parameters = new DialogParameters();
var options = new DialogOptions
{
CloseOnEscapeKey = true,
MaxWidth = MaxWidth.Medium,
FullWidth = true
};
var dialog = await DialogService.ShowAsync<ImportConfigDialog>("Import Config", parameters, options);
var result = await dialog.Result;
if (!result.Canceled)
{
Snackbar.Add("Config imported successfully", Severity.Success);
await State.LoadConfigsAsync();
}
}
}
```
## 🎨 Customization
### Thay đổi Theme
Component sử dụng MudBlazor theme. Để custom theme:
```csharp
builder.Services.AddMudServices(config =>
{
config.SnackbarConfiguration.PositionClass = Defaults.Classes.Position.BottomRight;
config.SnackbarConfiguration.VisibleStateDuration = 3000;
});
```
### Custom Styling
Thêm CSS vào `wwwroot/css/app.css`:
```css
.config-manager {
padding: 1rem;
}
.config-list-item {
cursor: pointer;
transition: background-color 0.2s;
}
.config-list-item:hover {
background-color: var(--mud-palette-action-hover);
}
```
## 🔧 Troubleshooting
### Component không hiển thị
**Kiểm tra:**
1. Đã đăng ký services trong `Program.cs`
2. Đã thêm using trong `_Imports.razor`
3. Đã có MudBlazor services
### API calls fail
**Kiểm tra:**
1. HttpClient có `BaseAddress` đúng
2. Backend API đang chạy
3. CORS đã được cấu hình (nếu frontend và backend khác domain)
### Export không hoạt động
**Kiểm tra:**
1. Đã copy `downloadFile.js` vào `wwwroot/js/`
2. Đã thêm script tag vào `index.html`
### State không update
**Kiểm tra:**
1. Đã subscribe `OnStateChanged` event
2. Đã gọi `StateHasChanged()` trong event handler
3. Component implement `IDisposable` và unsubscribe khi dispose
## 📚 Tài liệu tham khảo
- [Backend README](../RobotNet10.CustomConfiguration/README.md)
- [MudBlazor Documentation](https://mudblazor.com/)
- [Blazor Documentation](https://learn.microsoft.com/aspnet/core/blazor/)