563 lines
14 KiB
Markdown
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/)
|
|
|
|
|
|
|