Initial commit
This commit is contained in:
@@ -0,0 +1,562 @@
|
||||
# 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/)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user