583 lines
16 KiB
C#
583 lines
16 KiB
C#
using Microsoft.AspNetCore.Components.Authorization;
|
|
using RobotNet10.CustomConfigurationEditor.Models;
|
|
using RobotNet10.CustomConfigurationEditor.Services.API;
|
|
using System.Net.Http;
|
|
using System.Security.Claims;
|
|
using System.Threading;
|
|
|
|
namespace RobotNet10.CustomConfigurationEditor.Services.State;
|
|
|
|
/// <summary>
|
|
/// State management cho Config Manager
|
|
/// </summary>
|
|
public class ConfigManagerState(ConfigApiService apiService, AuthenticationStateProvider? authStateProvider = null, string editorRole = "")
|
|
{
|
|
private readonly ConfigApiService _apiService = apiService;
|
|
private readonly AuthenticationStateProvider? _authStateProvider = authStateProvider;
|
|
private readonly SemaphoreSlim _stateLock = new(1, 1);
|
|
|
|
// ===== DATA =====
|
|
public List<ConfigFileMetadataModel> Configs { get; private set; } = [];
|
|
public ConfigFileModel? SelectedConfig { get; private set; }
|
|
|
|
// ===== FILTERS & SEARCH =====
|
|
public string? SearchQuery { get; set; }
|
|
|
|
// ===== UI STATE =====
|
|
public bool IsLoading { get; private set; }
|
|
public bool IsSaving { get; private set; }
|
|
public string? ErrorMessage { get; private set; }
|
|
|
|
// ===== EVENTS =====
|
|
public event Action? OnStateChanged;
|
|
|
|
// ==========================================
|
|
// PUBLIC METHODS
|
|
// ==========================================
|
|
|
|
// Role Editor
|
|
public string EditorRole { get; } = editorRole;
|
|
|
|
/// <summary>
|
|
/// Load tất cả configs
|
|
/// </summary>
|
|
public async Task LoadConfigsAsync(string? searchQuery = null)
|
|
{
|
|
await _stateLock.WaitAsync();
|
|
try
|
|
{
|
|
IsLoading = true;
|
|
SearchQuery = searchQuery;
|
|
ErrorMessage = null;
|
|
NotifyStateChanged();
|
|
|
|
try
|
|
{
|
|
Configs = await _apiService.GetAllConfigsAsync(searchQuery);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
ErrorMessage = HttpErrorHelper.GetErrorMessage(ex);
|
|
Configs = [];
|
|
}
|
|
|
|
IsLoading = false;
|
|
NotifyStateChanged();
|
|
}
|
|
finally
|
|
{
|
|
_stateLock.Release();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Load config theo ID
|
|
/// </summary>
|
|
public async Task LoadConfigByIdAsync(Guid id)
|
|
{
|
|
await _stateLock.WaitAsync();
|
|
try
|
|
{
|
|
IsLoading = true;
|
|
ErrorMessage = null;
|
|
NotifyStateChanged();
|
|
|
|
try
|
|
{
|
|
SelectedConfig = await _apiService.GetConfigByIdAsync(id);
|
|
if (SelectedConfig == null)
|
|
{
|
|
ErrorMessage = "Config not found";
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
ErrorMessage = HttpErrorHelper.GetErrorMessage(ex);
|
|
SelectedConfig = null;
|
|
}
|
|
|
|
IsLoading = false;
|
|
NotifyStateChanged();
|
|
}
|
|
finally
|
|
{
|
|
_stateLock.Release();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Load config theo ConfigType
|
|
/// </summary>
|
|
public async Task LoadConfigByTypeAsync(string configType)
|
|
{
|
|
await _stateLock.WaitAsync();
|
|
try
|
|
{
|
|
IsLoading = true;
|
|
ErrorMessage = null;
|
|
NotifyStateChanged();
|
|
|
|
try
|
|
{
|
|
SelectedConfig = await _apiService.GetConfigByTypeAsync(configType);
|
|
if (SelectedConfig == null)
|
|
{
|
|
ErrorMessage = "Config not found";
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
ErrorMessage = HttpErrorHelper.GetErrorMessage(ex);
|
|
SelectedConfig = null;
|
|
}
|
|
|
|
IsLoading = false;
|
|
NotifyStateChanged();
|
|
}
|
|
finally
|
|
{
|
|
_stateLock.Release();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Select config
|
|
/// </summary>
|
|
public async Task SelectConfigAsync(ConfigFileMetadataModel configMetadata)
|
|
{
|
|
await LoadConfigByIdAsync(configMetadata.Id);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clear error message
|
|
/// </summary>
|
|
public void ClearError()
|
|
{
|
|
ErrorMessage = null;
|
|
NotifyStateChanged();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clear selection
|
|
/// </summary>
|
|
public void ClearSelection()
|
|
{
|
|
SelectedConfig = null;
|
|
NotifyStateChanged();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tạo config mới
|
|
/// </summary>
|
|
public async Task<ConfigFileModel> CreateConfigAsync(string configType, List<ConfigVariableModel> variables, string? description = null)
|
|
{
|
|
await _stateLock.WaitAsync();
|
|
try
|
|
{
|
|
IsSaving = true;
|
|
ErrorMessage = null;
|
|
NotifyStateChanged();
|
|
|
|
try
|
|
{
|
|
var config = await _apiService.CreateConfigAsync(configType, variables, description);
|
|
await ReloadConfigsInternalAsync();
|
|
SelectedConfig = config;
|
|
NotifyStateChanged();
|
|
return config;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
ErrorMessage = HttpErrorHelper.GetErrorMessage(ex);
|
|
throw;
|
|
}
|
|
finally
|
|
{
|
|
IsSaving = false;
|
|
NotifyStateChanged();
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
_stateLock.Release();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cập nhật config
|
|
/// </summary>
|
|
public async Task UpdateConfigAsync(List<ConfigVariableModel>? variables = null, string? description = null)
|
|
{
|
|
if (SelectedConfig == null)
|
|
{
|
|
throw new InvalidOperationException("No config selected");
|
|
}
|
|
|
|
await _stateLock.WaitAsync();
|
|
try
|
|
{
|
|
IsSaving = true;
|
|
ErrorMessage = null;
|
|
NotifyStateChanged();
|
|
|
|
try
|
|
{
|
|
SelectedConfig = await _apiService.UpdateConfigAsync(SelectedConfig.Id, variables, description);
|
|
await ReloadConfigsInternalAsync();
|
|
NotifyStateChanged();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
ErrorMessage = HttpErrorHelper.GetErrorMessage(ex);
|
|
throw;
|
|
}
|
|
finally
|
|
{
|
|
IsSaving = false;
|
|
NotifyStateChanged();
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
_stateLock.Release();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Xóa config
|
|
/// </summary>
|
|
public async Task DeleteConfigAsync(Guid id)
|
|
{
|
|
await _stateLock.WaitAsync();
|
|
try
|
|
{
|
|
IsSaving = true;
|
|
ErrorMessage = null;
|
|
NotifyStateChanged();
|
|
|
|
try
|
|
{
|
|
await _apiService.DeleteConfigAsync(id);
|
|
await ReloadConfigsInternalAsync();
|
|
|
|
// Clear selection if deleted
|
|
if (SelectedConfig?.Id == id)
|
|
{
|
|
SelectedConfig = null;
|
|
NotifyStateChanged();
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
ErrorMessage = HttpErrorHelper.GetErrorMessage(ex);
|
|
throw;
|
|
}
|
|
finally
|
|
{
|
|
IsSaving = false;
|
|
NotifyStateChanged();
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
_stateLock.Release();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Import config từ file
|
|
/// </summary>
|
|
public async Task<ConfigFileModel> ImportConfigAsync(Stream fileStream, string fileName, string configType, string? description = null)
|
|
{
|
|
await _stateLock.WaitAsync();
|
|
try
|
|
{
|
|
IsSaving = true;
|
|
ErrorMessage = null;
|
|
NotifyStateChanged();
|
|
|
|
try
|
|
{
|
|
var config = await _apiService.ImportConfigAsync(fileStream, fileName, configType, description);
|
|
await ReloadConfigsInternalAsync();
|
|
SelectedConfig = config;
|
|
NotifyStateChanged();
|
|
return config;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
ErrorMessage = HttpErrorHelper.GetErrorMessage(ex);
|
|
throw;
|
|
}
|
|
finally
|
|
{
|
|
IsSaving = false;
|
|
NotifyStateChanged();
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
_stateLock.Release();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Export config ra file
|
|
/// </summary>
|
|
public async Task<Stream> ExportConfigAsync(Guid id)
|
|
{
|
|
try
|
|
{
|
|
return await _apiService.ExportConfigAsync(id);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
ErrorMessage = HttpErrorHelper.GetErrorMessage(ex);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Cập nhật variable value
|
|
/// </summary>
|
|
public async Task UpdateVariableAsync(string variableName, object? value)
|
|
{
|
|
if (SelectedConfig == null)
|
|
{
|
|
throw new InvalidOperationException("No config selected");
|
|
}
|
|
|
|
await _stateLock.WaitAsync();
|
|
try
|
|
{
|
|
IsSaving = true;
|
|
ErrorMessage = null;
|
|
NotifyStateChanged();
|
|
|
|
try
|
|
{
|
|
SelectedConfig = await _apiService.UpdateVariableAsync(SelectedConfig.Id, variableName, value);
|
|
NotifyStateChanged();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
ErrorMessage = HttpErrorHelper.GetErrorMessage(ex);
|
|
throw;
|
|
}
|
|
finally
|
|
{
|
|
IsSaving = false;
|
|
NotifyStateChanged();
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
_stateLock.Release();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Thêm variable
|
|
/// </summary>
|
|
public async Task AddVariableAsync(ConfigVariableModel variable)
|
|
{
|
|
if (SelectedConfig == null)
|
|
{
|
|
throw new InvalidOperationException("No config selected");
|
|
}
|
|
|
|
await _stateLock.WaitAsync();
|
|
try
|
|
{
|
|
IsSaving = true;
|
|
ErrorMessage = null;
|
|
NotifyStateChanged();
|
|
|
|
try
|
|
{
|
|
SelectedConfig = await _apiService.AddVariableAsync(SelectedConfig.Id, variable);
|
|
NotifyStateChanged();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
ErrorMessage = HttpErrorHelper.GetErrorMessage(ex);
|
|
throw;
|
|
}
|
|
finally
|
|
{
|
|
IsSaving = false;
|
|
NotifyStateChanged();
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
_stateLock.Release();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Xóa variable
|
|
/// </summary>
|
|
public async Task RemoveVariableAsync(string variableName)
|
|
{
|
|
if (SelectedConfig == null)
|
|
{
|
|
throw new InvalidOperationException("No config selected");
|
|
}
|
|
|
|
await _stateLock.WaitAsync();
|
|
try
|
|
{
|
|
IsSaving = true;
|
|
ErrorMessage = null;
|
|
NotifyStateChanged();
|
|
|
|
try
|
|
{
|
|
SelectedConfig = await _apiService.RemoveVariableAsync(SelectedConfig.Id, variableName);
|
|
NotifyStateChanged();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
ErrorMessage = HttpErrorHelper.GetErrorMessage(ex);
|
|
throw;
|
|
}
|
|
finally
|
|
{
|
|
IsSaving = false;
|
|
NotifyStateChanged();
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
_stateLock.Release();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Kiểm tra ConfigType có tồn tại không
|
|
/// </summary>
|
|
public async Task<bool> ConfigTypeExistsAsync(string configType)
|
|
{
|
|
try
|
|
{
|
|
return await _apiService.ConfigTypeExistsAsync(configType);
|
|
}
|
|
catch (HttpRequestException)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// ==========================================
|
|
// ROLE-BASED PERMISSION CHECKS
|
|
// ==========================================
|
|
|
|
/// <summary>
|
|
/// Kiểm tra user hiện tại có quyền chỉnh sửa config không
|
|
/// </summary>
|
|
public async Task<bool> CanEditConfigAsync()
|
|
{
|
|
// Nếu EditorRole rỗng, mọi thứ hoạt động bình thường
|
|
if (string.IsNullOrWhiteSpace(EditorRole))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Kiểm tra role của user hiện tại
|
|
var userRoles = await GetCurrentUserRolesAsync();
|
|
return userRoles.Contains(EditorRole, StringComparer.OrdinalIgnoreCase);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Kiểm tra user hiện tại có quyền chỉnh sửa variable không
|
|
/// </summary>
|
|
public async Task<bool> CanEditVariableAsync(ConfigVariableModel variable)
|
|
{
|
|
// Nếu EditorRole rỗng, mọi thứ hoạt động bình thường
|
|
if (string.IsNullOrWhiteSpace(EditorRole))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
var userRoles = await GetCurrentUserRolesAsync();
|
|
|
|
// Kiểm tra nếu user có role = EditorRole
|
|
if (userRoles.Contains(EditorRole, StringComparer.OrdinalIgnoreCase))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Kiểm tra nếu role của user nằm trong Roles của variable
|
|
if (!string.IsNullOrWhiteSpace(variable.Roles))
|
|
{
|
|
var variableRoles = variable.Roles.Split(',', StringSplitOptions.RemoveEmptyEntries)
|
|
.Select(r => r.Trim())
|
|
.Where(r => !string.IsNullOrWhiteSpace(r));
|
|
|
|
return variableRoles.Any(role => userRoles.Contains(role, StringComparer.OrdinalIgnoreCase));
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Lấy danh sách roles của user hiện tại
|
|
/// </summary>
|
|
private async Task<List<string>> GetCurrentUserRolesAsync()
|
|
{
|
|
if (_authStateProvider == null)
|
|
{
|
|
return [];
|
|
}
|
|
|
|
try
|
|
{
|
|
var authState = await _authStateProvider.GetAuthenticationStateAsync();
|
|
var user = authState?.User;
|
|
|
|
if (user == null || user.Identity?.IsAuthenticated != true)
|
|
{
|
|
return [];
|
|
}
|
|
|
|
// Lấy roles từ claims
|
|
var roles = user.Claims
|
|
.Where(c => c.Type == ClaimTypes.Role)
|
|
.Select(c => c.Value)
|
|
.ToList();
|
|
|
|
return roles;
|
|
}
|
|
catch (Exception ex) when (ex is InvalidOperationException or NullReferenceException)
|
|
{
|
|
return [];
|
|
}
|
|
}
|
|
|
|
// ==========================================
|
|
// PRIVATE METHODS
|
|
// ==========================================
|
|
|
|
/// <summary>
|
|
/// Reload configs without acquiring the lock (for use inside locked methods)
|
|
/// </summary>
|
|
private async Task ReloadConfigsInternalAsync()
|
|
{
|
|
try
|
|
{
|
|
Configs = await _apiService.GetAllConfigsAsync(SearchQuery);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
ErrorMessage = HttpErrorHelper.GetErrorMessage(ex);
|
|
Configs = [];
|
|
}
|
|
}
|
|
|
|
private void NotifyStateChanged()
|
|
{
|
|
OnStateChanged?.Invoke();
|
|
}
|
|
}
|