# 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/)