# 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
```
### 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();
builder.Services.AddScoped();
```
### 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
```
### 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
Configuration Manager
```
### Cách 2: Sử dụng trong Layout
Thêm vào navigation menu:
```razor
Configuration Manager
```
## 🔧 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
Load Configs
@if (State.IsLoading)
{
}
@if (!string.IsNullOrEmpty(State.ErrorMessage))
{
@State.ErrorMessage
}
@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
```
**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
```
### 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
```
### 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
```
**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("Import Config");
var result = await dialog.Result;
```
#### ExportConfigDialog
Dialog để export config.
```razor
var dialog = await DialogService.ShowAsync("Export Config");
```
#### CreateConfigDialog
Dialog để tạo config mới.
```razor
var dialog = await DialogService.ShowAsync("Create Config");
```
#### EditConfigDialog
Dialog để chỉnh sửa metadata của config.
```razor
var dialog = await DialogService.ShowAsync("Edit Config");
```
## 📦 Models
### ConfigFileModel
```csharp
public class ConfigFileModel
{
public Guid Id { get; set; }
public string ConfigType { get; set; } = string.Empty;
public List 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? EnumValues { get; set; }
}
```
## 💡 Ví dụ
### Ví dụ 1: Custom Page với State
```razor
@page "/my-configs"
@inject ConfigManagerState State
@inject ISnackbar Snackbar
My Configs
My Configurations
@if (State.IsLoading)
{
}
@foreach (var config in State.Configs)
{
@config.ConfigType
@config.Description
Select
}
@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
Name
Type
Value
Actions
@context.Name
@context.Type
@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
Import Config
@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("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/)