dbb3a78afb
WBS-9.3 - NULL Policy CI Gate / NULL Policy Validation (push) Failing after 6s
Quant Engine CI/CD Pipeline / validate-core (push) Failing after 10s
Quant Engine CI/CD Pipeline / validate-ui-and-storage (push) Has been skipped
Deploy to Production / Build & Deploy to Production (push) Failing after 1m5s
267 lines
9.7 KiB
Plaintext
267 lines
9.7 KiB
Plaintext
@page "/users"
|
|
@attribute [Authorize]
|
|
@using MudBlazor
|
|
@inject HttpClient Http
|
|
@inject ISnackbar Snackbar
|
|
@inject IDialogService DialogService
|
|
|
|
<PageTitle>QuantEngine - 사용자 관리</PageTitle>
|
|
|
|
<!-- Page Header -->
|
|
<div class="mb-6">
|
|
<MudText Typo="Typo.h4" Class="mb-2">사용자 관리</MudText>
|
|
<MudText Typo="Typo.body1" Class="text-muted">시스템 사용자 및 권한 관리</MudText>
|
|
</div>
|
|
|
|
<!-- Action Bar -->
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<MudTextField @bind-Value="SearchQuery" Placeholder="사용자 검색..."
|
|
StartAdornment="@Icons.Material.Filled.Search"
|
|
Style="width: 300px;" />
|
|
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="OpenAddUserDialog">
|
|
<MudIcon Icon="@Icons.Material.Filled.Add" Class="mr-2" />
|
|
새 사용자 추가
|
|
</MudButton>
|
|
</div>
|
|
|
|
<!-- Users Table -->
|
|
<MudPaper Class="pa-4" Elevation="1">
|
|
@if (_users.Count == 0)
|
|
{
|
|
<MudAlert Severity="Severity.Info">사용자가 없습니다.</MudAlert>
|
|
}
|
|
else
|
|
{
|
|
<MudTable Items="@FilteredUsers" Dense="true" Hover="true" Striped="true">
|
|
<HeaderContent>
|
|
<MudTh>이름</MudTh>
|
|
<MudTh>역할</MudTh>
|
|
<MudTh>상태</MudTh>
|
|
<MudTh>생성일</MudTh>
|
|
<MudTh>수정일</MudTh>
|
|
<MudTh>작업</MudTh>
|
|
</HeaderContent>
|
|
<RowTemplate>
|
|
<MudTd DataLabel="Name">
|
|
<div class="d-flex align-items-center gap-2">
|
|
<MudAvatar Size="Size.Small" Color="Color.Primary">@context.Username[0].ToString().ToUpper()</MudAvatar>
|
|
<MudText Typo="Typo.body2">@context.Username</MudText>
|
|
</div>
|
|
</MudTd>
|
|
<MudTd DataLabel="Role">
|
|
<MudChip T="string" Label="true" Size="Size.Small"
|
|
Color="@(context.Role == "Admin" ? Color.Primary : (context.Role == "Operator" ? Color.Secondary : Color.Default))"
|
|
Variant="Variant.Filled">
|
|
@context.Role
|
|
</MudChip>
|
|
</MudTd>
|
|
<MudTd DataLabel="Status">
|
|
<MudChip T="string" Label="true" Size="Size.Small"
|
|
Color="@(context.IsActive ? Color.Success : Color.Warning)"
|
|
Variant="Variant.Filled">
|
|
@(context.IsActive ? "활성" : "비활성")
|
|
</MudChip>
|
|
</MudTd>
|
|
<MudTd DataLabel="Joined">
|
|
<MudText Typo="Typo.body2">@FormatDate(context.CreatedAt)</MudText>
|
|
</MudTd>
|
|
<MudTd DataLabel="Updated">
|
|
<MudText Typo="Typo.body2">@FormatDate(context.UpdatedAt)</MudText>
|
|
</MudTd>
|
|
<MudTd DataLabel="Actions">
|
|
<MudButton Variant="Variant.Text" Size="Size.Small" Color="Color.Primary" OnClick="@(() => EditUser(context))">편집</MudButton>
|
|
<MudButton Variant="Variant.Text" Size="Size.Small" Color="Color.Error" OnClick="@(() => DeleteUser(context))">삭제</MudButton>
|
|
</MudTd>
|
|
</RowTemplate>
|
|
</MudTable>
|
|
}
|
|
</MudPaper>
|
|
|
|
<!-- Add/Edit Dialog -->
|
|
<MudDialog @bind-Visible="_dialogVisible" Options="_dialogOptions">
|
|
<TitleContent>
|
|
<MudText Typo="Typo.h6">
|
|
<MudIcon Icon="@(_isEditMode ? Icons.Material.Filled.Edit : Icons.Material.Filled.Add)" Class="mr-3" />
|
|
@(_isEditMode ? "사용자 편집" : "새 사용자 추가")
|
|
</MudText>
|
|
</TitleContent>
|
|
<DialogContent>
|
|
<MudForm Model="@_formModel" @ref="_form">
|
|
<MudTextField T="string" @bind-Value="_formModel.Username" Label="사용자 ID" Required="true" Disabled="@_isEditMode"
|
|
RequiredError="사용자 ID를 입력해 주세요." Class="mb-3" />
|
|
|
|
<MudTextField T="string" @bind-Value="_formModel.Password" Label="@(_isEditMode ? "새 비밀번호 (미입력시 유지)" : "비밀번호")"
|
|
InputType="InputType.Password" Required="@(!_isEditMode)" RequiredError="비밀번호를 입력해 주세요." Class="mb-3" />
|
|
|
|
<MudSelect T="string" @bind-Value="_formModel.Role" Label="역할 권한" Required="true" Class="mb-3">
|
|
<MudSelectItem Value="@("Admin")">Admin (관리자)</MudSelectItem>
|
|
<MudSelectItem Value="@("Operator")">Operator (운영자)</MudSelectItem>
|
|
<MudSelectItem Value="@("Viewer")">Viewer (조회자)</MudSelectItem>
|
|
</MudSelect>
|
|
|
|
@if (_isEditMode)
|
|
{
|
|
<MudSwitch T="bool" @bind-Value="_formModel.IsActive" Color="Color.Success" Label="계정 활성화 상태" />
|
|
}
|
|
</MudForm>
|
|
</DialogContent>
|
|
<DialogActions>
|
|
<MudButton Variant="Variant.Text" Color="Color.Default" OnClick="CloseDialog" Class="px-5">취소</MudButton>
|
|
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="SaveUser" Class="px-5">저장</MudButton>
|
|
</DialogActions>
|
|
</MudDialog>
|
|
|
|
@code {
|
|
private List<UserDto> _users = new();
|
|
private string SearchQuery = "";
|
|
private bool _dialogVisible;
|
|
private bool _isEditMode;
|
|
private MudForm _form = new();
|
|
private UserFormModel _formModel = new();
|
|
private DialogOptions _dialogOptions = new() { MaxWidth = MaxWidth.Small, FullWidth = true, CloseButton = true };
|
|
|
|
private IEnumerable<UserDto> FilteredUsers
|
|
{
|
|
get => string.IsNullOrEmpty(SearchQuery)
|
|
? _users
|
|
: _users.Where(u => u.Username.Contains(SearchQuery, StringComparison.OrdinalIgnoreCase));
|
|
}
|
|
|
|
protected override async Task OnInitializedAsync()
|
|
{
|
|
await LoadUsers();
|
|
}
|
|
|
|
private async Task LoadUsers()
|
|
{
|
|
try
|
|
{
|
|
var res = await Http.GetFromJsonAsync<List<UserDto>>("api/users");
|
|
if (res != null)
|
|
{
|
|
_users = res;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Snackbar.Add($"사용자 목록 로드 실패: {ex.Message}", Severity.Error);
|
|
}
|
|
}
|
|
|
|
private void OpenAddUserDialog()
|
|
{
|
|
_isEditMode = false;
|
|
_formModel = new UserFormModel { Role = "Viewer", IsActive = true };
|
|
_dialogVisible = true;
|
|
}
|
|
|
|
private void EditUser(UserDto user)
|
|
{
|
|
_isEditMode = true;
|
|
_formModel = new UserFormModel
|
|
{
|
|
Username = user.Username,
|
|
Role = user.Role,
|
|
IsActive = user.IsActive,
|
|
Password = "" // Clear password field for security
|
|
};
|
|
_dialogVisible = true;
|
|
}
|
|
|
|
private async Task DeleteUser(UserDto user)
|
|
{
|
|
bool? result = await DialogService.ShowMessageBox(
|
|
"사용자 삭제",
|
|
$"정말로 사용자 '{user.Username}' 계정을 비활성화하시겠습니까?",
|
|
yesText: "비활성화", cancelText: "취소");
|
|
|
|
if (result == true)
|
|
{
|
|
try
|
|
{
|
|
var response = await Http.DeleteAsync($"api/users?username={user.Username}");
|
|
if (response.IsSuccessStatusCode)
|
|
{
|
|
Snackbar.Add("사용자 계정이 비활성화되었습니다.", Severity.Success);
|
|
await LoadUsers();
|
|
}
|
|
else
|
|
{
|
|
Snackbar.Add("계정 비활성화 작업에 실패했습니다.", Severity.Error);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Snackbar.Add($"API 에러: {ex.Message}", Severity.Error);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void CloseDialog()
|
|
{
|
|
_dialogVisible = false;
|
|
}
|
|
|
|
private async Task SaveUser()
|
|
{
|
|
await _form.Validate();
|
|
if (!_form.IsValid) return;
|
|
|
|
try
|
|
{
|
|
HttpResponseMessage response;
|
|
if (_isEditMode)
|
|
{
|
|
response = await Http.PutAsJsonAsync("api/users", _formModel);
|
|
}
|
|
else
|
|
{
|
|
response = await Http.PostAsJsonAsync("api/users", _formModel);
|
|
}
|
|
|
|
if (response.IsSuccessStatusCode)
|
|
{
|
|
Snackbar.Add("사용자 정보가 성공적으로 저장되었습니다.", Severity.Success);
|
|
_dialogVisible = false;
|
|
await LoadUsers();
|
|
}
|
|
else
|
|
{
|
|
var error = await response.Content.ReadAsStringAsync();
|
|
Snackbar.Add($"저장 실패: {error}", Severity.Error);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Snackbar.Add($"API 오류 발생: {ex.Message}", Severity.Error);
|
|
}
|
|
}
|
|
|
|
private string FormatDate(string isoString)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(isoString)) return "-";
|
|
if (DateTime.TryParse(isoString, out var dt))
|
|
{
|
|
return dt.ToLocalTime().ToString("yyyy-MM-dd HH:mm");
|
|
}
|
|
return isoString;
|
|
}
|
|
|
|
public class UserDto
|
|
{
|
|
public string Username { get; set; } = string.Empty;
|
|
public string Role { get; set; } = string.Empty;
|
|
public bool IsActive { get; set; }
|
|
public string CreatedAt { get; set; } = string.Empty;
|
|
public string UpdatedAt { get; set; } = string.Empty;
|
|
}
|
|
|
|
public class UserFormModel
|
|
{
|
|
public string Username { get; set; } = string.Empty;
|
|
public string Password { get; set; } = string.Empty;
|
|
public string Role { get; set; } = "Viewer";
|
|
public bool IsActive { get; set; } = true;
|
|
}
|
|
}
|