@page "/monitoring" @attribute [Authorize] @inject HttpClient Http @inject ISnackbar Snackbar QuantEngine - 데이터 수집 모니터링
데이터 수집 모니터링 실시간 수집 작업 상태 및 에러 추적
새로고침
@if (_loading) { } 진행 중인 작업 @_runningCount 완료 @_completedCount 실패 @_failedCount 총 스냅샷 @_totalSnapshots
@if (_recentRuns.Count == 0 && !_loading) { 최근 실행 기록이 없습니다. } else { 실행 ID 시작 시간 종료 시간 상태 스냅샷 에러 @context.RunId @FormatTime(context.StartedAt) @(string.IsNullOrEmpty(context.FinishedAt) ? "-" : FormatTime(context.FinishedAt)) @context.Status @(context.TotalSnapshots?.ToString() ?? "-") @if (context.TotalErrors > 0) { @context.TotalErrors } else { - } }
@if (_errors.Count == 0 && !_loading) { 에러가 없습니다. } else { @foreach (var error in _errors) {
[@error.ErrorKind] @error.ErrorMessage @FormatTime(error.CreatedAt)
Run: @error.RunId @if (!string.IsNullOrEmpty(error.Ticker)) { Ticker: @error.Ticker }
}
}
@code { private bool _loading = false; private int _runningCount; private int _completedCount; private int _failedCount; private int _totalSnapshots; private List _recentRuns = new(); private List _errors = new(); protected override async Task OnInitializedAsync() { await RefreshAsync(); } private async Task RefreshAsync() { _loading = true; StateHasChanged(); try { // 최근 실행 목록 로드 var runsResponse = await Http.GetFromJsonAsync("api/collection/runs?limit=20"); if (runsResponse?.Runs is not null) { _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); } // 대시보드 상태 로드 (전체 오류 목록) var state = await Http.GetFromJsonAsync("api/collection/state"); if (state?.RecentErrors is not null) { _errors = state.RecentErrors; } } catch (Exception ex) { Snackbar.Add($"데이터 로드 실패: {ex.Message}", Severity.Error); } finally { _loading = false; } } private Color GetStatusColor(string status) => status?.ToLowerInvariant() switch { "running" => Color.Info, "completed" => Color.Success, "pass" => Color.Success, "failed" => Color.Error, "error" => Color.Error, _ => Color.Warning }; private string FormatTime(string? isoTime) { if (string.IsNullOrEmpty(isoTime)) return "-"; return DateTimeOffset.TryParse(isoTime, out var dt) ? dt.LocalDateTime.ToString("yyyy-MM-dd HH:mm:ss") : isoTime; } // 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); }