diff --git a/src/dotnet/QuantEngine.Web/Client/Layout/MainLayout.razor b/src/dotnet/QuantEngine.Web/Client/Layout/MainLayout.razor index 435ed14..8426bcd 100644 --- a/src/dotnet/QuantEngine.Web/Client/Layout/MainLayout.razor +++ b/src/dotnet/QuantEngine.Web/Client/Layout/MainLayout.razor @@ -133,7 +133,7 @@ { var customProvider = (CustomAuthenticationStateProvider)AuthStateProvider; await customProvider.LogoutFromServerAsync(); - NavigationManager.NavigateTo("/Account/Login?handler=Logout", forceLoad: true); + NavigationManager.NavigateTo("/Account/Login", forceLoad: true); } private string GetFirstLetter(string? name) diff --git a/src/dotnet/QuantEngine.Web/Client/Layout/NavMenu.razor b/src/dotnet/QuantEngine.Web/Client/Layout/NavMenu.razor index 0218dd2..4f3a0dd 100644 --- a/src/dotnet/QuantEngine.Web/Client/Layout/NavMenu.razor +++ b/src/dotnet/QuantEngine.Web/Client/Layout/NavMenu.razor @@ -15,13 +15,4 @@ 운영 리포트 - - - - - -
- QuantEngine System - v2.1.0-Release -
diff --git a/src/dotnet/QuantEngine.Web/Client/Pages/Dashboard.razor b/src/dotnet/QuantEngine.Web/Client/Pages/Dashboard.razor index 7b39ff6..efd0554 100644 --- a/src/dotnet/QuantEngine.Web/Client/Pages/Dashboard.razor +++ b/src/dotnet/QuantEngine.Web/Client/Pages/Dashboard.razor @@ -1,6 +1,6 @@ @page "/dashboard" @rendermode InteractiveWebAssembly -@attribute [Microsoft.AspNetCore.Authorization.AllowAnonymous] + @using QuantEngine.Core.Infrastructure @using Microsoft.AspNetCore.Components.Authorization @inject HttpClient Http @@ -9,8 +9,7 @@ QuantEngine - Admin Dashboard - - +
@@ -244,26 +243,15 @@ protected override async Task OnInitializedAsync() { - // TEMPORARY: Disable auth check to verify dashboard loads - Console.WriteLine("[Dashboard] Page loading (auth check disabled for testing)"); - - // Check authentication var authState = await AuthStateProvider.GetAuthenticationStateAsync(); - Console.WriteLine($"[Dashboard] Auth state: IsAuthenticated={authState.User.Identity?.IsAuthenticated}, Name={authState.User.Identity?.Name}"); - - // TEMPORARY: Skip redirect, allow page to load - // if (!authState.User.Identity?.IsAuthenticated ?? true) - // { - // Console.WriteLine("[Dashboard] Not authenticated. Redirecting to login..."); - // NavManager.NavigateTo("/login.html", forceLoad: true); - // return; - // } - - Console.WriteLine($"[Dashboard] Page allowed to load"); + if (!(authState.User.Identity?.IsAuthenticated ?? false)) + { + NavManager.NavigateTo("/Account/Login", forceLoad: true); + return; + } try { - // Load operational report var report = await Http.GetFromJsonAsync("api/operational-report"); if (report != null) { @@ -276,7 +264,6 @@ // Handle error silently } - // Load recent activities LoadRecentActivities(); } diff --git a/src/dotnet/QuantEngine.Web/Client/Pages/DataCollectionMonitoring.razor b/src/dotnet/QuantEngine.Web/Client/Pages/DataCollectionMonitoring.razor index 99cfbae..0ede6ec 100644 --- a/src/dotnet/QuantEngine.Web/Client/Pages/DataCollectionMonitoring.razor +++ b/src/dotnet/QuantEngine.Web/Client/Pages/DataCollectionMonitoring.razor @@ -1,39 +1,54 @@ @page "/monitoring" @attribute [Authorize] @inject HttpClient Http +@inject ISnackbar Snackbar QuantEngine - 데이터 수집 모니터링
- 데이터 수집 모니터링 - 실시간 수집 작업 상태 및 에러 추적 +
+
+ 데이터 수집 모니터링 + 실시간 수집 작업 상태 및 에러 추적 +
+ + + 새로고침 + +
+@if (_loading) +{ + +} + 진행 중인 작업 - @RunningCount + @_runningCount 완료 - @CompletedCount + @_completedCount 실패 - @FailedCount + @_failedCount - 대기 중 - @PendingCount + 총 스냅샷 + @_totalSnapshots @@ -44,30 +59,30 @@
- @if (RecentRuns.Count == 0) + @if (_recentRuns.Count == 0 && !_loading) { 최근 실행 기록이 없습니다. } else { - + 실행 ID 시작 시간 종료 시간 상태 - 수집된 항목 - 작업 + 스냅샷 + 에러 @context.RunId - @context.StartTime.ToString("yyyy-MM-dd HH:mm:ss") + @FormatTime(context.StartedAt) - @(context.EndTime?.ToString("yyyy-MM-dd HH:mm:ss") ?? "-") + @(string.IsNullOrEmpty(context.FinishedAt) ? "-" : FormatTime(context.FinishedAt)) - - @context.ItemCount + + @(context.TotalSnapshots?.ToString() ?? "-") - - - 상세 - + + @if (context.TotalErrors > 0) + { + + @context.TotalErrors + + } + else + { + - + } @@ -96,22 +117,25 @@
- @if (Errors.Count == 0) + @if (_errors.Count == 0 && !_loading) { 에러가 없습니다. } else { - @foreach (var error in Errors) + @foreach (var error in _errors) {
- @error.Message - @error.Timestamp.ToString("yyyy-MM-dd HH:mm:ss") + [@error.ErrorKind] @error.ErrorMessage + @FormatTime(error.CreatedAt)
- Run ID: @error.RunId - @error.StackTrace + Run: @error.RunId + @if (!string.IsNullOrEmpty(error.Ticker)) + { + Ticker: @error.Ticker + }
}
@@ -119,173 +143,87 @@
- - - -
- - - @foreach (var ticker in CollectionStatus) - { -
-
- @ticker.Ticker - - @(ticker.IsSuccessful ? "성공" : "실패") - -
- - 마지막 수집: @ticker.LastCollectionTime.ToString("yyyy-MM-dd HH:mm:ss") - - - 데이터 포인트: @(ticker.DataPointCount)개 - -
- } -
-
-
-
@code { - // Status counts - private int RunningCount = 2; - private int CompletedCount = 156; - private int FailedCount = 8; - private int PendingCount = 5; + private bool _loading = false; + private int _runningCount; + private int _completedCount; + private int _failedCount; + private int _totalSnapshots; - // Recent runs - private List RecentRuns = new(); - - // Errors - private List Errors = new(); - - // Collection status - private List CollectionStatus = new(); + private List _recentRuns = new(); + private List _errors = new(); protected override async Task OnInitializedAsync() { - await LoadData(); + await RefreshAsync(); } - private async Task LoadData() + private async Task RefreshAsync() { - // Load recent runs - RecentRuns = new List - { - new RunModel - { - RunId = "RUN-2026-07-05-001", - StartTime = DateTime.Now.AddMinutes(-45), - EndTime = DateTime.Now.AddMinutes(-40), - Status = "완료", - ItemCount = 142 - }, - new RunModel - { - RunId = "RUN-2026-07-05-002", - StartTime = DateTime.Now.AddMinutes(-30), - EndTime = null, - Status = "진행 중", - ItemCount = 87 - }, - new RunModel - { - RunId = "RUN-2026-07-04-012", - StartTime = DateTime.Now.AddHours(-8).AddMinutes(-15), - EndTime = DateTime.Now.AddHours(-8).AddMinutes(-5), - Status = "완료", - ItemCount = 189 - } - }; + _loading = true; + StateHasChanged(); - // Load errors - Errors = new List + try { - new ErrorModel + // 최근 실행 목록 로드 + var runsResponse = await Http.GetFromJsonAsync("api/collection/runs?limit=20"); + if (runsResponse?.Runs is not null) { - RunId = "RUN-2026-07-04-011", - Message = "API Rate Limit Exceeded", - StackTrace = "Exception at CollectionService.FetchData()", - Timestamp = DateTime.Now.AddHours(-2) - }, - new ErrorModel - { - RunId = "RUN-2026-07-03-015", - Message = "Connection Timeout", - StackTrace = "Exception at HttpClient.GetAsync()", - Timestamp = DateTime.Now.AddHours(-5) + _recentRuns = runsResponse.Runs; + _runningCount = _recentRuns.Count(r => string.Equals(r.Status, "running", StringComparison.OrdinalIgnoreCase)); + _completedCount = _recentRuns.Count(r => string.Equals(r.Status, "completed", StringComparison.OrdinalIgnoreCase) + || string.Equals(r.Status, "PASS", StringComparison.OrdinalIgnoreCase)); + _failedCount = _recentRuns.Count(r => string.Equals(r.Status, "failed", StringComparison.OrdinalIgnoreCase) + || string.Equals(r.Status, "error", StringComparison.OrdinalIgnoreCase)); + _totalSnapshots = _recentRuns.Sum(r => r.TotalSnapshots ?? 0); } - }; - // Load collection status - CollectionStatus = new List + // 대시보드 상태 로드 (전체 오류 목록) + var state = await Http.GetFromJsonAsync("api/collection/state"); + if (state?.RecentErrors is not null) + { + _errors = state.RecentErrors; + } + } + catch (Exception ex) { - new CollectionStatusModel - { - Ticker = "005930", - IsSuccessful = true, - LastCollectionTime = DateTime.Now.AddMinutes(-2), - DataPointCount = 1450 - }, - new CollectionStatusModel - { - Ticker = "000660", - IsSuccessful = true, - LastCollectionTime = DateTime.Now.AddMinutes(-5), - DataPointCount = 1203 - }, - new CollectionStatusModel - { - Ticker = "051910", - IsSuccessful = false, - LastCollectionTime = DateTime.Now.AddHours(-1), - DataPointCount = 945 - } - }; - - await Task.CompletedTask; + Snackbar.Add($"데이터 로드 실패: {ex.Message}", Severity.Error); + } + finally + { + _loading = false; + } } - private Color GetStatusColor(string status) => status switch + private Color GetStatusColor(string status) => status?.ToLowerInvariant() switch { - "완료" => Color.Success, - "진행 중" => Color.Info, - "실패" => Color.Error, - _ => Color.Warning + "running" => Color.Info, + "completed" => Color.Success, + "pass" => Color.Success, + "failed" => Color.Error, + "error" => Color.Error, + _ => Color.Warning }; - private async Task ViewRunDetails(RunModel run) + private string FormatTime(string? isoTime) { - // View details dialog - await Task.CompletedTask; + if (string.IsNullOrEmpty(isoTime)) return "-"; + return DateTimeOffset.TryParse(isoTime, out var dt) + ? dt.LocalDateTime.ToString("yyyy-MM-dd HH:mm:ss") + : isoTime; } - private class RunModel - { - public string RunId { get; set; } - public DateTime StartTime { get; set; } - public DateTime? EndTime { get; set; } - public string Status { get; set; } - public int ItemCount { get; set; } - } - - private class ErrorModel - { - public string RunId { get; set; } - public string Message { get; set; } - public string StackTrace { get; set; } - public DateTime Timestamp { get; set; } - } - - private class CollectionStatusModel - { - public string Ticker { get; set; } - public bool IsSuccessful { get; set; } - public DateTime LastCollectionTime { get; set; } - public int DataPointCount { get; set; } - } + // DTOs (shared with ApiClient) + private record CollectionRunsResponse(List Runs, int Count); + private record CollectionRunDto( + string RunId, string Status, string StartedAt, + string? FinishedAt, int? TotalSnapshots, int? TotalErrors); + private record CollectionDashboardStateDto( + string? LastRunId, string? LastRunStatus, string? LastFinishedAt, + int TotalSnapshots, int TotalErrors, List RecentErrors); + private record CollectionErrorDto( + string RunId, string SourceName, string ErrorKind, + string ErrorMessage, string? Ticker, string CreatedAt); }