Files
Denso/srcs/RobotNet10/Components/RobotNet10.CustomConfigurationEditor
2026-07-03 16:31:37 +07:00
..
2026-07-03 16:31:37 +07:00
2026-07-03 16:31:37 +07:00
2026-07-03 16:31:37 +07:00
2026-07-03 16:31:37 +07:00
2026-07-03 16:31:37 +07:00
2026-07-03 16:31:37 +07:00

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

  • 🎨 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

<ItemGroup>
  <ProjectReference Include="..\Components\RobotNet10.CustomConfigurationEditor\RobotNet10.CustomConfigurationEditor.csproj" />
</ItemGroup>

2. Đăng ký Services

Trong Program.cs hoặc Client/Program.cs:

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:

<script src="js/downloadFile.js"></script>

4. Thêm Using

Trong _Imports.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:

@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:

<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:

@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:

@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:

<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:

<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:

<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:

<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.

var dialog = await DialogService.ShowAsync<ImportConfigDialog>("Import Config");
var result = await dialog.Result;

ExportConfigDialog

Dialog để export config.

var dialog = await DialogService.ShowAsync<ExportConfigDialog>("Export Config");

CreateConfigDialog

Dialog để tạo config mới.

var dialog = await DialogService.ShowAsync<CreateConfigDialog>("Create Config");

EditConfigDialog

Dialog để chỉnh sửa metadata của config.

var dialog = await DialogService.ShowAsync<EditConfigDialog>("Edit Config");

📦 Models

ConfigFileModel

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

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

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

@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

@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

@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:

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:

.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