diff --git a/src/dotnet/QuantEngine.Web/Client/Components/ConfirmDialog.razor b/src/dotnet/QuantEngine.Web/Client/Components/ConfirmDialog.razor deleted file mode 100644 index 02094a1e..00000000 --- a/src/dotnet/QuantEngine.Web/Client/Components/ConfirmDialog.razor +++ /dev/null @@ -1,61 +0,0 @@ -@namespace QuantEngine.Web.Client.Components -@inject IDialogService DialogService - -@code { - public static async Task Show(IDialogService dialogService, string title, string message, string confirmText = "확인", string cancelText = "취소") - { - var options = new DialogOptions - { - CloseButton = false, - MaxWidth = MaxWidth.Small, - FullWidth = true, - BackdropClick = false - }; - - var parameters = new DialogParameters - { - { x => x.Title, title }, - { x => x.Message, message }, - { x => x.ConfirmText, confirmText }, - { x => x.CancelText, cancelText } - }; - - var dialog = await dialogService.ShowAsync(title, parameters, options); - var result = await dialog.Result; - - return !result.Canceled && (bool?)result.Data == true; - } -} - - - - - @Title - @Message - - - - @CancelText - @ConfirmText - - - -@code { - [CascadingParameter] - private IMudDialogInstance MudDialog { get; set; } - - [Parameter] - public string Title { get; set; } = "확인"; - - [Parameter] - public string Message { get; set; } = ""; - - [Parameter] - public string ConfirmText { get; set; } = "확인"; - - [Parameter] - public string CancelText { get; set; } = "취소"; - - private void Confirm() => MudDialog.Close(DialogResult.Ok(true)); - private void Cancel() => MudDialog.Cancel(); -} diff --git a/src/dotnet/QuantEngine.Web/Client/Components/FormField.razor b/src/dotnet/QuantEngine.Web/Client/Components/FormField.razor deleted file mode 100644 index b5e00d97..00000000 --- a/src/dotnet/QuantEngine.Web/Client/Components/FormField.razor +++ /dev/null @@ -1,125 +0,0 @@ -@namespace QuantEngine.Web.Client.Components - - - - - @switch (Type) - { - case "text": - case "email": - case "password": - case "number": - - break; - - case "textarea": - - break; - - case "select": - - @foreach (var option in Options) - { - @option - } - - break; - - case "checkbox": - - @Label - - break; - - case "date": - - break; - } - - @if (!string.IsNullOrEmpty(HelpText)) - { - @HelpText - } - - -@code { - [Parameter] - public string Label { get; set; } = ""; - - [Parameter] - public string Type { get; set; } = "text"; - - [Parameter] - public string Value { get; set; } = ""; - - [Parameter] - public EventCallback ValueChanged { get; set; } - - [Parameter] - public string Placeholder { get; set; } = ""; - - [Parameter] - public bool Required { get; set; } = false; - - [Parameter] - public string ErrorMessage { get; set; } = ""; - - [Parameter] - public string HelpText { get; set; } = ""; - - [Parameter] - public List Options { get; set; } = new(); -} - - diff --git a/src/dotnet/QuantEngine.Web/Client/Infrastructure/CustomAuthenticationStateProvider.cs b/src/dotnet/QuantEngine.Web/Client/Infrastructure/CustomAuthenticationStateProvider.cs deleted file mode 100644 index 1c5bdcd3..00000000 --- a/src/dotnet/QuantEngine.Web/Client/Infrastructure/CustomAuthenticationStateProvider.cs +++ /dev/null @@ -1,249 +0,0 @@ -using System.Security.Claims; -using Microsoft.AspNetCore.Components.Authorization; -using Microsoft.JSInterop; -using QuantEngine.Web.Client.Services; - -namespace QuantEngine.Web.Client.Infrastructure -{ - public class CustomAuthenticationStateProvider : AuthenticationStateProvider - { - private readonly LocalStorageService _localStorage; - private readonly HttpClient _http; - private readonly IJSRuntime _jsRuntime; - private readonly ClaimsPrincipal _anonymous = new ClaimsPrincipal(new ClaimsIdentity()); - private const string TokenKey = "quant_admin_access_token"; - private const string UsernameKey = "quant_admin_username"; - private const string RoleKey = "quant_admin_role"; - private const string RememberUsernameKey = "quant_admin_remember_username"; - - private AuthenticationState? _cachedState; - - public CustomAuthenticationStateProvider(LocalStorageService localStorage, HttpClient http, IJSRuntime jsRuntime) - { - _localStorage = localStorage; - _http = http; - _jsRuntime = jsRuntime; - } - - public override async Task GetAuthenticationStateAsync() - { - if (_cachedState != null && _cachedState.User.Identity?.IsAuthenticated == true) - { - Console.WriteLine("[Auth] Returning cached authentication state"); - return _cachedState; - } - - try - { - Console.WriteLine("[Auth] GetAuthenticationStateAsync called"); - - // Primary: Try to validate via /api/auth/me - // This works with both cookies (automatic) and Bearer tokens - try - { - Console.WriteLine("[Auth] Attempting validation via /api/auth/me (cookie or Bearer)..."); - // BaseAddress is always set to HostEnvironment.BaseAddress by DI. - // Never fall back to a hardcoded port — it breaks in production. - var meUrl = "api/auth/me"; - var requestUri = _http.BaseAddress == null ? new Uri($"http://localhost:5265/{meUrl}") : new Uri(_http.BaseAddress, meUrl); - Console.WriteLine($"[Auth] /api/auth/me URL: {requestUri}"); - - var meResponse = await _http.GetAsync(requestUri); - Console.WriteLine($"[Auth] /api/auth/me status: {meResponse.StatusCode}"); - - if (meResponse.IsSuccessStatusCode) - { - var json = await meResponse.Content.ReadAsStringAsync(); - Console.WriteLine($"[Auth] Response JSON: {json}"); - - var meData = System.Text.Json.JsonDocument.Parse(json).RootElement; - var authenticated = meData.TryGetProperty("authenticated", out var authProp) && authProp.GetBoolean(); - var username = meData.TryGetProperty("username", out var userProp) ? userProp.GetString() : null; - var role = meData.TryGetProperty("role", out var roleProp) ? roleProp.GetString() : "Admin"; - - Console.WriteLine($"[Auth] Parsed: authenticated={authenticated}, username={username}, role={role}"); - - if (authenticated && !string.IsNullOrWhiteSpace(username)) - { - Console.WriteLine($"[Auth] ✅ SUCCESS: Authenticated as {username}"); - var identity = new ClaimsIdentity(new[] - { - new Claim(ClaimTypes.Name, username), - new Claim(ClaimTypes.Role, role ?? "Admin") - }, "QuantAdminAuth"); - - var state = new AuthenticationState(new ClaimsPrincipal(identity)); - _cachedState = state; - return state; - } - else - { - Console.WriteLine($"[Auth] Parsing failed: authenticated={authenticated}, username={username}"); - } - } - else - { - Console.WriteLine($"[Auth] /api/auth/me returned {meResponse.StatusCode}"); - - if (IsLocalhost()) - { - Console.WriteLine("[Auth] Dev SSR fallback: allowing admin authentication on 401"); - return GetDevAdminState(); - } - } - } - catch (Exception meEx) - { - Console.WriteLine($"[Auth] /api/auth/me failed: {meEx.Message}"); - Console.WriteLine($"[Auth] Exception: {meEx}"); - - if (IsLocalhost()) - { - Console.WriteLine("[Auth] Dev SSR fallback: allowing admin authentication on exception"); - return GetDevAdminState(); - } - } - - // Fallback: Try to read from localStorage - Console.WriteLine("[Auth] Fallback: checking localStorage..."); - try - { - string token = await _jsRuntime.InvokeAsync("localStorage.getItem", TokenKey); - string username = await _jsRuntime.InvokeAsync("localStorage.getItem", UsernameKey); - string role = await _jsRuntime.InvokeAsync("localStorage.getItem", RoleKey); - - Console.WriteLine($"[Auth] localStorage: token={!string.IsNullOrWhiteSpace(token)}, username={username}"); - - if (!string.IsNullOrWhiteSpace(token) && !string.IsNullOrWhiteSpace(username)) - { - var meUrl = "api/auth/me"; - var requestUri = _http.BaseAddress == null ? new Uri($"http://localhost:5265/{meUrl}") : new Uri(_http.BaseAddress, meUrl); - var request = new HttpRequestMessage(HttpMethod.Get, requestUri); - request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); - var response = await _http.SendAsync(request); - - if (response.IsSuccessStatusCode) - { - Console.WriteLine($"[Auth] ✅ localStorage token validated: {username}"); - var identity = new ClaimsIdentity(new[] - { - new Claim(ClaimTypes.Name, username), - new Claim(ClaimTypes.Role, role ?? "Admin") - }, "QuantAdminAuth"); - - var state = new AuthenticationState(new ClaimsPrincipal(identity)); - _cachedState = state; - return state; - } - } - } - catch (Exception jsEx) - { - Console.WriteLine($"[Auth] localStorage fallback failed: {jsEx.Message}"); - } - - Console.WriteLine("[Auth] ❌ Not authenticated"); - } - catch (Exception ex) - { - Console.WriteLine($"[Auth] Unexpected error: {ex.Message}"); - } - - _cachedState = new AuthenticationState(_anonymous); - return _cachedState; - } - - public async Task MarkUserAsAuthenticatedAsync(string username, string accessToken, string role) - { - await MarkUserAsAuthenticatedAsync(username, accessToken, role, rememberUsername: true); - } - - public async Task MarkUserAsAuthenticatedAsync(string username, string accessToken, string role, bool rememberUsername) - { - await _localStorage.SetAsync(TokenKey, accessToken); - if (rememberUsername) - { - await _localStorage.SetAsync(UsernameKey, username); - await _localStorage.SetAsync(RememberUsernameKey, true); - } - else - { - await _localStorage.DeleteAsync(UsernameKey); - await _localStorage.SetAsync(RememberUsernameKey, false); - } - await _localStorage.SetAsync(RoleKey, role); - - var identity = new ClaimsIdentity(new[] - { - new Claim(ClaimTypes.Name, username), - new Claim(ClaimTypes.Role, role) - }, "QuantAdminAuth"); - - var user = new ClaimsPrincipal(identity); - var state = new AuthenticationState(user); - _cachedState = state; - NotifyAuthenticationStateChanged(Task.FromResult(state)); - } - - public async Task MarkUserAsLoggedOutAsync() - { - await _localStorage.DeleteAsync(TokenKey); - await _localStorage.DeleteAsync(RoleKey); - var rememberUsername = await _localStorage.GetAsync(RememberUsernameKey); - if (!rememberUsername) - { - await _localStorage.DeleteAsync(UsernameKey); - } - _cachedState = new AuthenticationState(_anonymous); - NotifyAuthenticationStateChanged(Task.FromResult(_cachedState)); - } - - public async Task LogoutFromServerAsync() - { - var token = await _localStorage.GetAsync(TokenKey); - if (!string.IsNullOrWhiteSpace(token)) - { - try - { - var request = new HttpRequestMessage(HttpMethod.Post, "api/auth/logout"); - request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token); - await _http.SendAsync(request); - } - catch - { - // Best-effort server revocation; always clear local state. - } - } - - await MarkUserAsLoggedOutAsync(); - } - - public async Task GetRememberedUsernameAsync() - { - var rememberUsername = await _localStorage.GetAsync(RememberUsernameKey); - if (!rememberUsername) - { - return null; - } - - return await _localStorage.GetAsync(UsernameKey); - } - - private bool IsLocalhost() - { - return _http.BaseAddress == null || _http.BaseAddress.Host == "localhost" || _http.BaseAddress.Host == "127.0.0.1"; - } - - private AuthenticationState GetDevAdminState() - { - var identity = new ClaimsIdentity(new[] - { - new Claim(ClaimTypes.Name, "admin"), - new Claim(ClaimTypes.Role, "Admin") - }, "QuantAdminAuth"); - var state = new AuthenticationState(new ClaimsPrincipal(identity)); - _cachedState = state; - return state; - } - } -} diff --git a/src/dotnet/QuantEngine.Web/Client/Layout/AuthLayout.razor b/src/dotnet/QuantEngine.Web/Client/Layout/AuthLayout.razor deleted file mode 100644 index 5a11cf84..00000000 --- a/src/dotnet/QuantEngine.Web/Client/Layout/AuthLayout.razor +++ /dev/null @@ -1,20 +0,0 @@ -@inherits LayoutComponentBase -@rendermode InteractiveWebAssembly - - - -@Body - -@code { -} diff --git a/src/dotnet/QuantEngine.Web/Client/Layout/AuthLayout.razor.css b/src/dotnet/QuantEngine.Web/Client/Layout/AuthLayout.razor.css deleted file mode 100644 index 5659d06e..00000000 --- a/src/dotnet/QuantEngine.Web/Client/Layout/AuthLayout.razor.css +++ /dev/null @@ -1,260 +0,0 @@ -/* QuantEngine AuthLayout Styles */ - -.auth-container { - display: flex; - min-height: 100vh; - background: linear-gradient(135deg, var(--mud-palette-primary) 0%, var(--mud-palette-primary-dark) 100%); - font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; -} - -/* Left Panel - Branding */ -.auth-left-panel { - flex: 1; - display: flex; - flex-direction: column; - justify-content: space-between; - align-items: center; - padding: 3rem; - color: white; - position: relative; -} - -.auth-branding { - display: flex; - flex-direction: column; - align-items: center; - text-align: center; - flex: 1; - justify-content: center; -} - -.auth-logo { - margin-bottom: 2rem; - animation: float 3s ease-in-out infinite; -} - -.auth-logo ::deep svg { - filter: drop-shadow(0 4px 6px rgba(0, 0, 0, 0.1)); - font-size: 80px; - color: white; -} - -.auth-title { - font-weight: 700; - margin-bottom: 0.5rem; - letter-spacing: 1px; -} - -.auth-subtitle { - opacity: 0.9; - font-size: 1.1rem; - max-width: 300px; -} - -.auth-features { - margin-top: 3rem; - display: flex; - flex-direction: column; - gap: 1.5rem; - align-items: flex-start; - width: 100%; - max-width: 300px; -} - -.auth-feature { - display: flex; - align-items: center; - gap: 1rem; - opacity: 0.95; -} - -.auth-feature ::deep svg { - font-size: 24px; - color: #4caf50; - flex-shrink: 0; -} - -.auth-theme-toggle { - position: absolute; - top: 2rem; - right: 2rem; -} - -.auth-theme-toggle ::deep button { - color: white; - transition: transform 0.2s ease; -} - -.auth-theme-toggle ::deep button:hover { - transform: scale(1.1); -} - -/* Right Panel - Auth Content */ -.auth-right-panel { - flex: 1; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - padding: 2rem; - background: var(--mud-palette-background); - position: relative; -} - -.auth-mobile-header { - display: flex; - justify-content: space-between; - align-items: center; - width: 100%; - margin-bottom: 2rem; - padding-bottom: 1rem; - border-bottom: 1px solid var(--mud-palette-divider); -} - -.auth-mobile-header ::deep .mud-icon { - color: var(--mud-palette-primary); -} - -.auth-content { - width: 100%; - max-width: 450px; -} - -.auth-content ::deep .mud-card { - background: var(--mud-palette-surface); - border: 1px solid var(--mud-palette-divider); - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); -} - -.auth-content ::deep .mud-form-control { - margin-bottom: 1.5rem; -} - -.auth-content ::deep .mud-button { - text-transform: none; - font-weight: 600; - padding: 0.75rem 1.5rem; -} - -.auth-content ::deep .mud-button-root { - border-radius: 0.4rem; -} - -/* Footer */ -.auth-footer { - position: absolute; - bottom: 2rem; - text-align: center; - width: 100%; - padding: 1rem 2rem; - border-top: 1px solid var(--mud-palette-divider); -} - -.auth-footer-text { - display: block; - color: var(--mud-palette-text-secondary); - margin-bottom: 0.5rem; -} - -.auth-footer-links { - display: flex; - justify-content: center; - align-items: center; - gap: 0.75rem; -} - -.auth-footer-links ::deep a { - color: var(--mud-palette-primary); - text-decoration: none; - transition: color 0.2s ease; -} - -.auth-footer-links ::deep a:hover { - color: var(--mud-palette-primary-dark); - text-decoration: underline; -} - -/* Responsive */ -@media (max-width: 960px) { - .auth-container { - flex-direction: column; - } - - .auth-left-panel { - padding: 2rem; - min-height: 40vh; - } - - .auth-right-panel { - padding: 3rem 2rem 5rem; - min-height: 60vh; - } - - .auth-mobile-header { - display: flex; - } - - .auth-footer { - bottom: 1rem; - padding: 1rem; - } -} - -@media (max-width: 600px) { - .auth-right-panel { - padding: 2rem 1rem 5rem; - } - - .auth-content { - max-width: 100%; - } - - .auth-features { - max-width: 100%; - } - - .auth-footer { - position: static; - padding: 1rem; - border-top: 1px solid var(--mud-palette-divider); - margin-top: 3rem; - } -} - -/* Animation */ -@keyframes float { - 0%, 100% { - transform: translateY(0); - } - 50% { - transform: translateY(-10px); - } -} - -/* Dark Mode */ -[data-theme="dark"] .auth-container { - background: linear-gradient(135deg, #1e1e2e 0%, #2d2d44 100%); -} - -[data-theme="dark"] .auth-left-panel { - color: #f0f0f0; -} - -[data-theme="dark"] .auth-right-panel { - background: #121212; -} - -/* Accessibility */ -@media (prefers-reduced-motion: reduce) { - .auth-logo { - animation: none; - } - - .auth-theme-toggle ::deep button { - transition: none; - } - - .auth-footer-links ::deep a { - transition: none; - } -} diff --git a/src/dotnet/QuantEngine.Web/Client/Layout/EmptyLayout.razor b/src/dotnet/QuantEngine.Web/Client/Layout/EmptyLayout.razor deleted file mode 100644 index 479db95f..00000000 --- a/src/dotnet/QuantEngine.Web/Client/Layout/EmptyLayout.razor +++ /dev/null @@ -1,16 +0,0 @@ -@inherits LayoutComponentBase - -@Body - - diff --git a/src/dotnet/QuantEngine.Web/Client/Layout/MainLayout.razor b/src/dotnet/QuantEngine.Web/Client/Layout/MainLayout.razor deleted file mode 100644 index cf6e2a46..00000000 --- a/src/dotnet/QuantEngine.Web/Client/Layout/MainLayout.razor +++ /dev/null @@ -1,154 +0,0 @@ -@inherits LayoutComponentBase -@using QuantEngine.Web.Client.Theme -@inject HttpClient Http -@inject AuthenticationStateProvider AuthStateProvider -@inject NavigationManager NavigationManager - - - - - - - - - - - - - - - - - QuantEngine - - - - - - - - - - - @GetFirstLetter(authContext.User.Identity?.Name) - - - - - - @authContext.User.Identity?.Name - - - - - - 프로필 - - - - 설정 - - - - - 로그아웃 - - - - - - - - - - - 메뉴 - - - - - - - - - - - - - - - - - @Body - - - - -@code { - private MudTheme _theme = AppTheme.LightTheme; - private bool navOpen = true; - private bool fixedOpen = true; - private string appVersion = "Local Debug"; - private string buildTime = "N/A"; - - protected override async Task OnInitializedAsync() - { - try - { - var versionInfo = await Http.GetFromJsonAsync("version.json"); - if (versionInfo != null) - { - appVersion = versionInfo.Version ?? "Local Debug"; - buildTime = versionInfo.Built ?? "N/A"; - } - } - catch - { - } - - await base.OnInitializedAsync(); - } - - private void ToggleDrawer() - { - navOpen = !navOpen; - } - - private async Task HandleLogoutAsync() - { - var customProvider = (CustomAuthenticationStateProvider)AuthStateProvider; - await customProvider.LogoutFromServerAsync(); - NavigationManager.NavigateTo("/Account/Login", forceLoad: true); - } - - private string GetFirstLetter(string? name) - { - return string.IsNullOrEmpty(name) ? "?" : name[0].ToString().ToUpper(); - } - - private string GetUserInitials() - { - return string.Empty; - } - - private class VersionInfo - { - public string? Version { get; set; } - public string? Built { get; set; } - } -} diff --git a/src/dotnet/QuantEngine.Web/Client/Layout/MainLayout.razor.css b/src/dotnet/QuantEngine.Web/Client/Layout/MainLayout.razor.css deleted file mode 100644 index 480a48ca..00000000 --- a/src/dotnet/QuantEngine.Web/Client/Layout/MainLayout.razor.css +++ /dev/null @@ -1,105 +0,0 @@ -/* QuantEngine MainLayout Styles */ - -/* AppBar Enhancements */ -.mud-appbar-dense { - padding: 0 1rem; -} - -.mud-appbar-dense ::deep .mud-appbar-section-center { - flex: 1; -} - -/* Avatar Styling */ -::deep .mud-avatar { - cursor: pointer; - transition: transform 0.2s ease; -} - -::deep .mud-avatar:hover { - transform: scale(1.05); -} - -/* Drawer Footer */ -.mud-drawer-footer { - position: absolute; - bottom: 0; - width: 100%; - background: var(--mud-palette-surface); -} - -/* Main Content Area */ -.mud-main-content-enhanced { - min-height: 100vh; - background: var(--mud-palette-background); - transition: background-color 0.3s ease; -} - -/* Navigation Menu Styles */ -.mud-navmenu { - padding: 1rem 0; -} - -.mud-navmenu ::deep .mud-nav-item { - padding: 0.5rem 0; - margin: 0.25rem 0; -} - -.mud-navmenu ::deep .mud-nav-link { - border-radius: 0.4rem; - margin: 0 0.5rem; - transition: all 0.2s ease; -} - -.mud-navmenu ::deep .mud-nav-link:hover { - background-color: var(--mud-palette-action-default-hover); -} - -.mud-navmenu ::deep .mud-nav-link.mud-ripple-nav-link-active { - background-color: var(--mud-palette-primary-lighten); - color: var(--mud-palette-primary); - font-weight: 600; -} - -/* Responsive Drawer */ -@media (max-width: 599px) { - .mud-drawer-content { - width: 100% !important; - } - - .mud-drawer-footer { - position: relative; - } -} - -@media (min-width: 600px) { - .mud-drawer-footer { - position: absolute; - } -} - -/* Error UI */ -#blazor-error-ui { - color-scheme: light only; - background: lightyellow; - bottom: 0; - box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); - box-sizing: border-box; - display: none; - left: 0; - padding: 0.6rem 1.25rem 0.7rem 1.25rem; - position: fixed; - width: 100%; - z-index: 1000; -} - -#blazor-error-ui .dismiss { - cursor: pointer; - position: absolute; - right: 0.75rem; - top: 0.5rem; -} - -/* Dark Mode Transitions */ -* { - transition: background-color 0.3s ease, color 0.3s ease; -} diff --git a/src/dotnet/QuantEngine.Web/Client/Layout/NavMenu.razor b/src/dotnet/QuantEngine.Web/Client/Layout/NavMenu.razor deleted file mode 100644 index 4f3a0ddd..00000000 --- a/src/dotnet/QuantEngine.Web/Client/Layout/NavMenu.razor +++ /dev/null @@ -1,18 +0,0 @@ - - - - 대시보드 - - - - - 사용자 관리 - 데이터 수집 - 수집 모니터링 - - - - - 운영 리포트 - - diff --git a/src/dotnet/QuantEngine.Web/Client/Layout/NavMenu.razor.css b/src/dotnet/QuantEngine.Web/Client/Layout/NavMenu.razor.css deleted file mode 100644 index a2aeace9..00000000 --- a/src/dotnet/QuantEngine.Web/Client/Layout/NavMenu.razor.css +++ /dev/null @@ -1,105 +0,0 @@ -.navbar-toggler { - appearance: none; - cursor: pointer; - width: 3.5rem; - height: 2.5rem; - color: white; - position: absolute; - top: 0.5rem; - right: 1rem; - border: 1px solid rgba(255, 255, 255, 0.1); - background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e") no-repeat center/1.75rem rgba(255, 255, 255, 0.1); -} - -.navbar-toggler:checked { - background-color: rgba(255, 255, 255, 0.5); -} - -.top-row { - min-height: 3.5rem; - background-color: rgba(0,0,0,0.4); -} - -.navbar-brand { - font-size: 1.1rem; -} - -.bi { - display: inline-block; - position: relative; - width: 1.25rem; - height: 1.25rem; - margin-right: 0.75rem; - top: -1px; - background-size: cover; -} - -.bi-house-door-fill-nav-menu { - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-house-door-fill' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z'/%3E%3C/svg%3E"); -} - -.bi-plus-square-fill-nav-menu { - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-plus-square-fill' viewBox='0 0 16 16'%3E%3Cpath d='M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z'/%3E%3C/svg%3E"); -} - -.bi-list-nested-nav-menu { - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E"); -} - -.nav-item { - font-size: 0.9rem; - padding-bottom: 0.5rem; -} - - .nav-item:first-of-type { - padding-top: 1rem; - } - - .nav-item:last-of-type { - padding-bottom: 1rem; - } - - .nav-item ::deep .nav-link { - color: #d7d7d7; - background: none; - border: none; - border-radius: 4px; - height: 3rem; - display: flex; - align-items: center; - line-height: 3rem; - width: 100%; - } - -.nav-item ::deep a.active { - background-color: rgba(255,255,255,0.37); - color: white; -} - -.nav-item ::deep .nav-link:hover { - background-color: rgba(255,255,255,0.1); - color: white; -} - -.nav-scrollable { - display: none; -} - -.navbar-toggler:checked ~ .nav-scrollable { - display: block; -} - -@media (min-width: 641px) { - .navbar-toggler { - display: none; - } - - .nav-scrollable { - /* Never collapse the sidebar for wide screens */ - display: block; - - /* Allow sidebar to scroll for tall menus */ - height: calc(100vh - 3.5rem); - overflow-y: auto; - } -} diff --git a/src/dotnet/QuantEngine.Web/Client/Pages/Collection.razor b/src/dotnet/QuantEngine.Web/Client/Pages/Collection.razor deleted file mode 100644 index 42d9050e..00000000 --- a/src/dotnet/QuantEngine.Web/Client/Pages/Collection.razor +++ /dev/null @@ -1,155 +0,0 @@ -@page "/collection" -@attribute [Authorize] -@using QuantEngine.Web.Client.Services -@inject ApiClient ApiClient -@inject ILogger Logger - -QuantEngine - Collection - -Data Collection -KIS API data collection dashboard. API-first로만 동작합니다. - - - - @(IsProcessing ? "Running..." : "Start Collection") - - Refresh - - -@if (IsLoading) -{ - -} -else if (DashboardState != null) -{ - - - - Last Run - @(DashboardState.LastRunStatus ?? "N/A") - @(DashboardState.LastFinishedAt ?? "Not finished") - - - - - Total Snapshots - @DashboardState.TotalSnapshots - - - - - Total Errors - @DashboardState.TotalErrors - - - - - @if (DashboardState.RecentErrors.Count > 0) - { - - Recent Errors - - - Source - Kind - Ticker - Message - - - @context.SourceName - @context.ErrorKind - @context.Ticker - @context.ErrorMessage - - - - } - - @if (RecentRuns != null && RecentRuns.Count > 0) - { - - Recent Runs - - - Run ID - Status - Started - Finished - Snapshots - Errors - - - @context.RunId - @context.Status - @context.StartedAt - @context.FinishedAt - @context.TotalSnapshots - @context.TotalErrors - - - - } -} - -@code { - private CollectionDashboardStateDto? DashboardState; - private List? RecentRuns; - private bool IsLoading = true; - private bool IsProcessing = false; - - protected override async Task OnInitializedAsync() - { - await LoadDashboardStateAsync(); - } - - private async Task LoadDashboardStateAsync() - { - IsLoading = true; - try - { - // Parallelize API calls to avoid sequential RTT bottlenecks - var stateTask = ApiClient.GetCollectionStateAsync(); - var runsTask = ApiClient.GetCollectionRunsAsync(10); - - await Task.WhenAll(stateTask, runsTask); - - DashboardState = await stateTask; - var runsResponse = await runsTask; - RecentRuns = runsResponse?.Runs ?? new(); - } - catch (Exception ex) - { - Logger.LogError(ex, "Error loading dashboard"); - } - finally - { - IsLoading = false; - } - } - - private async Task StartCollectionAsync() - { - IsProcessing = true; - try - { - var result = await ApiClient.StartCollectionRunAsync(); - if (result != null) - { - await LoadDashboardStateAsync(); - } - } - catch (Exception ex) - { - Logger.LogError(ex, "Error starting collection"); - } - finally - { - IsProcessing = false; - } - } - - private async Task RefreshAsync() - { - await LoadDashboardStateAsync(); - } -} diff --git a/src/dotnet/QuantEngine.Web/Client/Pages/Dashboard.razor b/src/dotnet/QuantEngine.Web/Client/Pages/Dashboard.razor deleted file mode 100644 index efd05541..00000000 --- a/src/dotnet/QuantEngine.Web/Client/Pages/Dashboard.razor +++ /dev/null @@ -1,342 +0,0 @@ -@page "/dashboard" -@rendermode InteractiveWebAssembly - -@using QuantEngine.Core.Infrastructure -@using Microsoft.AspNetCore.Components.Authorization -@inject HttpClient Http -@inject AuthenticationStateProvider AuthStateProvider -@inject NavigationManager NavManager - -QuantEngine - Admin Dashboard - - - - -
- 관리자 대시보드 - 시스템 현황 및 데이터 수집 모니터링 -
- - - - - - -
-
- 총 수집 실행 - @TotalRuns - - - 이번 주 +@WeeklyRuns - -
- -
-
-
- - - - -
-
- 성공률 - @SuccessRate% - - - 최근 30일 - -
- -
-
-
- - - - -
-
- 최근 에러 - @RecentErrors - - - 지난 7일 - -
- -
-
-
- - - - -
-
- 마지막 동기화 - @LastSyncTime - - - @(IsLastSyncSuccess ? "성공" : "경고") - - -
- -
-
-
-
- - - - - - - 최근 활동 - - @if (RecentActivities.Count == 0) - { - 활동 기록이 없습니다. - } - else - { - - @foreach (var activity in RecentActivities) - { -
- -
- @activity.Title - @activity.Timestamp.ToString("yyyy-MM-dd HH:mm:ss") - @activity.Description -
-
- } -
- } -
-
- - - - - 시스템 상태 - - -
- API 서버 - 온라인 -
-
- 데이터베이스 - 연결됨 -
-
- KIS API - - @(KisApiStatus ? "활성" : "비활성") - -
- - 마지막 점검: @SystemCheckTime -
-
-
-
- - - -
- 최근 데이터 수집 실행 - - - 새로고침 - -
- - @if (Sections.Count == 0) - { - 데이터 수집 기록이 없습니다. - } - else - { - - - 이름 - 상태 - 시작 시간 - 작업 - - - - @context.Name - - - - @context.Title - - - - @context.Preview - - - 상세 - - - - } -
- - - -@code { - private readonly List Sections = new(); - private readonly List RecentActivities = new(); - - // KPI values - private int TotalRuns = 47; - private int WeeklyRuns = 12; - private int SuccessRate = 94; - private int RecentErrors = 3; - private string LastSyncTime = "2분 전"; - private bool IsLastSyncSuccess = true; - private bool KisApiStatus = true; - private string SystemCheckTime = DateTime.Now.ToString("HH:mm:ss"); - - protected override async Task OnInitializedAsync() - { - var authState = await AuthStateProvider.GetAuthenticationStateAsync(); - if (!(authState.User.Identity?.IsAuthenticated ?? false)) - { - NavManager.NavigateTo("/Account/Login", forceLoad: true); - return; - } - - try - { - var report = await Http.GetFromJsonAsync("api/operational-report"); - if (report != null) - { - Sections.Clear(); - Sections.AddRange(report.Sections); - } - } - catch - { - // Handle error silently - } - - LoadRecentActivities(); - } - - private void LoadRecentActivities() - { - RecentActivities.Clear(); - RecentActivities.AddRange(new[] - { - new ActivityLog - { - Type = "success", - Title = "데이터 수집 완료", - Description = "삼성전자(005930) 주가 데이터 수집 성공", - Timestamp = DateTime.Now.AddMinutes(-5) - }, - new ActivityLog - { - Type = "warning", - Title = "API 레이트 제한", - Description = "KIS API 레이트 제한에 도달했으나 재시도 예정", - Timestamp = DateTime.Now.AddMinutes(-12) - }, - new ActivityLog - { - Type = "success", - Title = "대시보드 업데이트", - Description = "포트폴리오 구성 분석 완료", - Timestamp = DateTime.Now.AddMinutes(-35) - }, - new ActivityLog - { - Type = "info", - Title = "스케줄 실행", - Description = "일일 정기 수집 작업 시작", - Timestamp = DateTime.Now.AddHours(-1) - } - }); - } - - private async Task RefreshData() - { - await OnInitializedAsync(); - } - - private string GetActivityIcon(string type) => type switch - { - "success" => Icons.Material.Filled.CheckCircle, - "warning" => Icons.Material.Filled.WarningAmber, - "error" => Icons.Material.Filled.Error, - _ => Icons.Material.Filled.Info - }; - - private string GetActivityColor(string type) => type switch - { - "success" => "#4caf50", - "warning" => "#ff9800", - "error" => "#f44336", - _ => "#2196f3" - }; - - private Color GetActivityColorEnum(string type) => type switch - { - "success" => Color.Success, - "warning" => Color.Warning, - "error" => Color.Error, - _ => Color.Info - }; - - private class ActivityLog - { - public string Type { get; set; } - public string Title { get; set; } - public string Description { get; set; } - public DateTime Timestamp { get; set; } - } -} diff --git a/src/dotnet/QuantEngine.Web/Client/Pages/DataCollectionMonitoring.razor b/src/dotnet/QuantEngine.Web/Client/Pages/DataCollectionMonitoring.razor deleted file mode 100644 index 0ede6ec3..00000000 --- a/src/dotnet/QuantEngine.Web/Client/Pages/DataCollectionMonitoring.razor +++ /dev/null @@ -1,229 +0,0 @@ -@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); -} diff --git a/src/dotnet/QuantEngine.Web/Client/Pages/NotFound.razor b/src/dotnet/QuantEngine.Web/Client/Pages/NotFound.razor deleted file mode 100644 index f72e0e1d..00000000 --- a/src/dotnet/QuantEngine.Web/Client/Pages/NotFound.razor +++ /dev/null @@ -1,8 +0,0 @@ -@page "/not-found" -@layout MainLayout - - - - -

Not Found

-

Sorry, the content you are looking for does not exist.

\ No newline at end of file diff --git a/src/dotnet/QuantEngine.Web/Client/Pages/Operations.razor b/src/dotnet/QuantEngine.Web/Client/Pages/Operations.razor deleted file mode 100644 index 5c94800e..00000000 --- a/src/dotnet/QuantEngine.Web/Client/Pages/Operations.razor +++ /dev/null @@ -1,121 +0,0 @@ -@page "/operations" -@attribute [Authorize] -@using QuantEngine.Core.Infrastructure -@inject HttpClient Http - -QuantEngine - Operations - -Operational Report -Temp/operational_report.json만 읽는 운영 고정 화면입니다. - - - - - Schema - @SchemaVersion - - - - - Sections - @SectionCountLabel - - - - - Source - @SourceJson - - - - - Generated - @GeneratedAt - - - - - - @foreach (var section in HighlightSections) - { - - - @(section.Name) - @(section.Title) - @(section.Preview) - - - } - - - - Report Health - - Status: @HealthLabel - Path: @ReportPath - Sections rendered: @RenderedSectionCountLabel - - - - - Sections - @if (Sections.Count == 0) - { - DATA_MISSING: operational_report.json에 표시할 섹션이 없습니다. - } - else - { - - - Name - Title - Preview - - - @context.Name - @context.Title - @context.Preview - - - } - - -@code { - private readonly List Sections = new(); - private readonly List HighlightSections = new(); - private string SchemaVersion = "n/a"; - private string SourceJson = "n/a"; - private string GeneratedAt = "n/a"; - private string SectionCountLabel = "0"; - private string RenderedSectionCountLabel = "0"; - private string HealthLabel = "DATA_MISSING"; - private string ReportPath = "n/a"; - - protected override async Task OnInitializedAsync() - { - try - { - var report = await Http.GetFromJsonAsync("api/operational-report"); - if (report != null) - { - SchemaVersion = report.SchemaVersion; - SourceJson = report.SourceJson; - GeneratedAt = report.GeneratedAt; - - Sections.Clear(); - Sections.AddRange(report.Sections); - - HighlightSections.Clear(); - HighlightSections.AddRange(Sections.Take(4)); - - SectionCountLabel = report.SectionCount.ToString(); - RenderedSectionCountLabel = Sections.Count.ToString(); - HealthLabel = Sections.Count > 0 ? "PASS" : "DATA_MISSING"; - } - } - catch - { - HealthLabel = "DATA_MISSING"; - } - } -} diff --git a/src/dotnet/QuantEngine.Web/Client/Pages/Portfolio.razor b/src/dotnet/QuantEngine.Web/Client/Pages/Portfolio.razor deleted file mode 100644 index da5b6353..00000000 --- a/src/dotnet/QuantEngine.Web/Client/Pages/Portfolio.razor +++ /dev/null @@ -1,238 +0,0 @@ -@page "/portfolio" -@attribute [Authorize] -@inject HttpClient Http - -QuantEngine - 포트폴리오 - - -
- 포트폴리오 - 자산 구성 및 성과 분석 -
- - - - - - 총 평가액 - ₩125.5M - +3.2% (이번 달) - - - - - - 보유 종목 - 12개 - 주식 및 펀드 - - - - - - 수익률 - +8.5% - 연간 기준 - - - - - - 위험도 - 중간 - - Moderate - - - - - - - - - - 자산 구성 - - - - 종목/펀드명 - 수량 - 현재가 - 평가액 - 수익률 - 비율 - - - -
- @context.Name[0] -
- @context.Name - @context.Ticker -
-
-
- - @context.Quantity.ToString("N0") - - - ₩@context.CurrentPrice.ToString("N0") - - - ₩@context.Value.ToString("N0") - - - - @(context.ReturnRate >= 0 ? "+" : "")@context.ReturnRate.ToString("F1")% - - - - @context.Ratio.ToString("F1")% - -
-
-
-
- - - - 자산 분류 - - - @foreach (var category in AssetCategories) - { -
-
- @category.Name - @category.Percentage% -
- -
- } -
-
-
-
- - - - 거래 이력 - - @if (TradingHistory.Count == 0) - { - 거래 이력이 없습니다. - } - else - { - - - 일자 - 종목 - 구분 - 수량 - 단가 - 금액 - 수수료 - - - - @context.Date.ToString("yyyy-MM-dd") - - - @context.Ticker - - - - @context.Type - - - - @context.Quantity - - - ₩@context.Price.ToString("N0") - - - ₩@context.Amount.ToString("N0") - - - ₩@context.Fee.ToString("N0") - - - - } - - -@code { - private List _assets = new(); - private List AssetCategories = new(); - private List TradingHistory = new(); - - protected override async Task OnInitializedAsync() - { - await LoadAssets(); - } - - private async Task LoadAssets() - { - _assets = new List - { - new AssetModel { Name = "삼성전자", Ticker = "005930", Quantity = 50, CurrentPrice = 70000, Value = 3500000, ReturnRate = 5.2M, Ratio = 28.0M }, - new AssetModel { Name = "LG화학", Ticker = "051910", Quantity = 30, CurrentPrice = 820000, Value = 24600000, ReturnRate = -2.1M, Ratio = 19.6M }, - new AssetModel { Name = "현대차", Ticker = "005380", Quantity = 40, CurrentPrice = 245000, Value = 9800000, ReturnRate = 8.5M, Ratio = 7.8M }, - new AssetModel { Name = "SK하이닉스", Ticker = "000660", Quantity = 25, CurrentPrice = 105000, Value = 2625000, ReturnRate = 12.3M, Ratio = 2.1M }, - new AssetModel { Name = "삼성중공업", Ticker = "010140", Quantity = 60, CurrentPrice = 85000, Value = 5100000, ReturnRate = 3.7M, Ratio = 4.1M }, - new AssetModel { Name = "포스코", Ticker = "005490", Quantity = 20, CurrentPrice = 75000, Value = 1500000, ReturnRate = -5.2M, Ratio = 1.2M }, - }; - - AssetCategories = new List - { - new CategoryModel { Name = "대형주", Percentage = 45, Color = Color.Primary }, - new CategoryModel { Name = "중형주", Percentage = 30, Color = Color.Secondary }, - new CategoryModel { Name = "소형주", Percentage = 15, Color = Color.Info }, - new CategoryModel { Name = "채권/현금", Percentage = 10, Color = Color.Success } - }; - - TradingHistory = new List - { - new TradeModel { Date = DateTime.Now.AddDays(-5), Ticker = "005930", Type = "매수", Quantity = 10, Price = 68000, Amount = 680000, Fee = 1360 }, - new TradeModel { Date = DateTime.Now.AddDays(-10), Ticker = "051910", Type = "매도", Quantity = 5, Price = 850000, Amount = 4250000, Fee = 8500 }, - new TradeModel { Date = DateTime.Now.AddDays(-15), Ticker = "005380", Type = "매수", Quantity = 20, Price = 240000, Amount = 4800000, Fee = 9600 }, - }; - - await Task.CompletedTask; - } - - private class AssetModel - { - public string Name { get; set; } - public string Ticker { get; set; } - public int Quantity { get; set; } - public decimal CurrentPrice { get; set; } - public decimal Value { get; set; } - public decimal ReturnRate { get; set; } - public decimal Ratio { get; set; } - } - - private class CategoryModel - { - public string Name { get; set; } - public int Percentage { get; set; } - public Color Color { get; set; } - } - - private class TradeModel - { - public DateTime Date { get; set; } - public string Ticker { get; set; } - public string Type { get; set; } - public int Quantity { get; set; } - public decimal Price { get; set; } - public decimal Amount { get; set; } - public decimal Fee { get; set; } - } -} diff --git a/src/dotnet/QuantEngine.Web/Client/Pages/Users.razor b/src/dotnet/QuantEngine.Web/Client/Pages/Users.razor deleted file mode 100644 index 128bc098..00000000 --- a/src/dotnet/QuantEngine.Web/Client/Pages/Users.razor +++ /dev/null @@ -1,268 +0,0 @@ -@page "/users" -@attribute [Authorize] -@using MudBlazor -@inject HttpClient Http -@inject ISnackbar Snackbar -@inject IDialogService DialogService - -QuantEngine - 사용자 관리 - - -
- 사용자 관리 - 시스템 사용자 및 권한 관리 -
- - -
- - - - 새 사용자 추가 - -
- - - - @if (_users.Count == 0) - { - 사용자가 없습니다. - } - else - { - - - 이름 - 역할 - 상태 - 생성일 - 수정일 - 작업 - - - -
- @context.Username[0].ToString().ToUpper() - @context.Username -
-
- - - @context.Role - - - - - @(context.IsActive ? "활성" : "비활성") - - - - @FormatDate(context.CreatedAt) - - - @FormatDate(context.UpdatedAt) - - - 편집 - 삭제 - -
-
- } -
- - - - - - - @(_isEditMode ? "사용자 편집" : "새 사용자 추가") - - - - - - - - - - Admin (관리자) - Operator (운영자) - Viewer (조회자) - - - @if (_isEditMode) - { - - } - - - - 취소 - 저장 - - - -@code { - private List _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 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 - { - // BaseAddress is set to HostEnvironment.BaseAddress by DI in Client/Program.cs. - // Never override it with a hardcoded port. - var res = await Http.GetFromJsonAsync>("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.ShowMessageBoxAsync( - "사용자 삭제", - $"정말로 사용자 '{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; - } -} diff --git a/src/dotnet/QuantEngine.Web/Client/Program.cs b/src/dotnet/QuantEngine.Web/Client/Program.cs deleted file mode 100644 index 2cb2a97c..00000000 --- a/src/dotnet/QuantEngine.Web/Client/Program.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Microsoft.AspNetCore.Components.WebAssembly.Hosting; -using Microsoft.AspNetCore.Components.Authorization; -using QuantEngine.Web.Client.Services; -using QuantEngine.Web.Client.Infrastructure; -using MudBlazor.Services; - -var builder = WebAssemblyHostBuilder.CreateDefault(args); - -// Register LocalStorage for cross-platform session persistence -builder.Services.AddScoped(); - -// App State Service (RBAC & global state management) -builder.Services.AddScoped(); - -// Authentication setup in WebAssembly client -builder.Services.AddAuthorizationCore(); -builder.Services.AddCascadingAuthenticationState(); -builder.Services.AddScoped(); - -// MudBlazor Services (CRITICAL: Required for Interactive WebAssembly) -builder.Services.AddMudServices(); - -// HttpClient register (API-First standard) -builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); -builder.Services.AddScoped(); - -await builder.Build().RunAsync(); diff --git a/src/dotnet/QuantEngine.Web/Client/QuantEngine.Web.Client.csproj b/src/dotnet/QuantEngine.Web/Client/QuantEngine.Web.Client.csproj deleted file mode 100644 index bc3d0642..00000000 --- a/src/dotnet/QuantEngine.Web/Client/QuantEngine.Web.Client.csproj +++ /dev/null @@ -1,22 +0,0 @@ - - - - net10.0 - enable - enable - true - Default - - - - - - - - - - - - - - diff --git a/src/dotnet/QuantEngine.Web/Client/RedirectToLogin.razor b/src/dotnet/QuantEngine.Web/Client/RedirectToLogin.razor deleted file mode 100644 index ab9645fc..00000000 --- a/src/dotnet/QuantEngine.Web/Client/RedirectToLogin.razor +++ /dev/null @@ -1,8 +0,0 @@ -@inject NavigationManager NavigationManager - -@code { - protected override void OnInitialized() - { - NavigationManager.NavigateTo("login"); - } -} diff --git a/src/dotnet/QuantEngine.Web/Client/Services/ApiClient.cs b/src/dotnet/QuantEngine.Web/Client/Services/ApiClient.cs deleted file mode 100644 index d6753c95..00000000 --- a/src/dotnet/QuantEngine.Web/Client/Services/ApiClient.cs +++ /dev/null @@ -1,221 +0,0 @@ -using System.Net.Http.Json; -using System.Text.Json.Serialization; -using QuantEngine.Core.Interfaces; - -namespace QuantEngine.Web.Client.Services; - -public class ApiClient -{ - private readonly HttpClient _http; - private readonly ILogger _logger; - public ApiClient(HttpClient http, ILogger logger) - { - _http = http; - // BaseAddress is set by the DI registration in Client/Program.cs via - // builder.HostEnvironment.BaseAddress — never hardcode a port here. - if (_http.BaseAddress == null) - { - throw new InvalidOperationException( - "ApiClient: HttpClient.BaseAddress is null. " + - "Ensure the HttpClient is registered with HostEnvironment.BaseAddress in Client/Program.cs."); - } - _logger = logger; - } - - // Collection API Methods - - public async Task GetCollectionStateAsync() - { - try - { - return await _http.GetFromJsonAsync("api/collection/state"); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error fetching collection state"); - return null; - } - } - - public async Task GetCollectionRunsAsync(int limit = 20) - { - try - { - return await _http.GetFromJsonAsync($"api/collection/runs?limit={limit}"); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error fetching collection runs"); - return null; - } - } - - public async Task GetCollectionSnapshotsAsync(string runId) - { - try - { - return await _http.GetFromJsonAsync($"api/collection/runs/{runId}/snapshots"); - } - catch (Exception ex) - { - _logger.LogError(ex, $"Error fetching snapshots for run {runId}"); - return null; - } - } - - public async Task GetCollectionErrorsAsync(string runId, int limit = 50) - { - try - { - return await _http.GetFromJsonAsync($"api/collection/runs/{runId}/errors?limit={limit}"); - } - catch (Exception ex) - { - _logger.LogError(ex, $"Error fetching errors for run {runId}"); - return null; - } - } - - public async Task StartCollectionRunAsync() - { - try - { - var response = await _http.PostAsJsonAsync("api/collection/run", new { }); - if (response.IsSuccessStatusCode) - { - return await response.Content.ReadFromJsonAsync(); - } - return null; - } - catch (Exception ex) - { - _logger.LogError(ex, "Error starting collection run"); - return null; - } - } -} - -// DTOs - -public class CollectionDashboardStateDto -{ - [JsonPropertyName("lastRunId")] - public string? LastRunId { get; set; } - - [JsonPropertyName("lastRunStatus")] - public string? LastRunStatus { get; set; } - - [JsonPropertyName("lastFinishedAt")] - public string? LastFinishedAt { get; set; } - - [JsonPropertyName("totalSnapshots")] - public int TotalSnapshots { get; set; } - - [JsonPropertyName("totalErrors")] - public int TotalErrors { get; set; } - - [JsonPropertyName("recentErrors")] - public List RecentErrors { get; set; } = new(); -} - -public class CollectionRunDto -{ - [JsonPropertyName("runId")] - public string RunId { get; set; } = ""; - - [JsonPropertyName("status")] - public string Status { get; set; } = ""; - - [JsonPropertyName("startedAt")] - public string StartedAt { get; set; } = ""; - - [JsonPropertyName("finishedAt")] - public string? FinishedAt { get; set; } - - [JsonPropertyName("totalSnapshots")] - public int? TotalSnapshots { get; set; } - - [JsonPropertyName("totalErrors")] - public int? TotalErrors { get; set; } -} - -public class CollectionSnapshotDto -{ - [JsonPropertyName("runId")] - public string RunId { get; set; } = ""; - - [JsonPropertyName("datasetName")] - public string DatasetName { get; set; } = ""; - - [JsonPropertyName("ticker")] - public string Ticker { get; set; } = ""; - - [JsonPropertyName("sourceName")] - public string SourceName { get; set; } = ""; - - [JsonPropertyName("capturedAt")] - public string CapturedAt { get; set; } = ""; -} - -public class CollectionErrorDto -{ - [JsonPropertyName("runId")] - public string RunId { get; set; } = ""; - - [JsonPropertyName("sourceName")] - public string SourceName { get; set; } = ""; - - [JsonPropertyName("errorKind")] - public string ErrorKind { get; set; } = ""; - - [JsonPropertyName("errorMessage")] - public string ErrorMessage { get; set; } = ""; - - [JsonPropertyName("ticker")] - public string Ticker { get; set; } = ""; -} - -public class CollectionRunsResponse -{ - [JsonPropertyName("runs")] - public List Runs { get; set; } = new(); - - [JsonPropertyName("count")] - public int Count { get; set; } -} - -public class CollectionRunSnapshotsResponse -{ - [JsonPropertyName("runId")] - public string RunId { get; set; } = ""; - - [JsonPropertyName("snapshots")] - public List Snapshots { get; set; } = new(); - - [JsonPropertyName("count")] - public int Count { get; set; } -} - -public class CollectionRunErrorsResponse -{ - [JsonPropertyName("runId")] - public string RunId { get; set; } = ""; - - [JsonPropertyName("errors")] - public List Errors { get; set; } = new(); - - [JsonPropertyName("count")] - public int Count { get; set; } -} - -public class CollectionRunStartResponse -{ - [JsonPropertyName("runId")] - public string RunId { get; set; } = ""; - - [JsonPropertyName("status")] - public string Status { get; set; } = ""; - - [JsonPropertyName("startedAt")] - public string StartedAt { get; set; } = ""; -} diff --git a/src/dotnet/QuantEngine.Web/Client/Services/AppStateService.cs b/src/dotnet/QuantEngine.Web/Client/Services/AppStateService.cs deleted file mode 100644 index 0c54372a..00000000 --- a/src/dotnet/QuantEngine.Web/Client/Services/AppStateService.cs +++ /dev/null @@ -1,142 +0,0 @@ -namespace QuantEngine.Web.Client.Services; - -public class AppStateService -{ - private UserContext _currentUser; - private List _userRoles = new(); - private bool _isInitialized = false; - - public event Action OnStateChanged; - - public UserContext CurrentUser - { - get => _currentUser; - set - { - _currentUser = value; - NotifyStateChanged(); - } - } - - public List UserRoles - { - get => _userRoles; - set - { - _userRoles = value; - NotifyStateChanged(); - } - } - - public bool IsInitialized - { - get => _isInitialized; - set - { - _isInitialized = value; - NotifyStateChanged(); - } - } - - public AppStateService() - { - _currentUser = new UserContext(); - _userRoles = new List(); - } - - /// - /// Initialize app state from current user context - /// - public async Task InitializeAsync(HttpClient httpClient) - { - try - { - var response = await httpClient.GetAsync("api/auth/user"); - if (response.IsSuccessStatusCode) - { - var content = await response.Content.ReadAsStringAsync(); - // Parse user info (implement as needed) - CurrentUser = new UserContext { Name = "Admin", Email = "admin@quantengine.local" }; - UserRoles = new List { "Admin" }; - } - } - catch - { - // Handle error - } - finally - { - IsInitialized = true; - } - } - - /// - /// Check if user has specific role (RBAC) - /// - public bool HasRole(string role) - { - return UserRoles.Contains(role); - } - - /// - /// Check if user has any of the specified roles - /// - public bool HasAnyRole(params string[] roles) - { - return roles.Any(r => UserRoles.Contains(r)); - } - - /// - /// Check if user has all specified roles - /// - public bool HasAllRoles(params string[] roles) - { - return roles.All(r => UserRoles.Contains(r)); - } - - /// - /// Clear user state - /// - public void Clear() - { - CurrentUser = new UserContext(); - UserRoles = new List(); - IsInitialized = false; - } - - private void NotifyStateChanged() => OnStateChanged?.Invoke(); -} - -/// -/// User context model -/// -public class UserContext -{ - public string Id { get; set; } = ""; - public string Name { get; set; } = ""; - public string Email { get; set; } = ""; - public DateTime CreatedAt { get; set; } = DateTime.Now; - public bool IsActive { get; set; } = true; -} - -/// -/// API Response wrapper -/// -public class ApiResponse -{ - public bool Success { get; set; } - public string Message { get; set; } - public T Data { get; set; } -} - -/// -/// Pagination model -/// -public class PaginatedResponse -{ - public List Items { get; set; } - public int PageNumber { get; set; } - public int PageSize { get; set; } - public int TotalCount { get; set; } - public int TotalPages => (TotalCount + PageSize - 1) / PageSize; -} diff --git a/src/dotnet/QuantEngine.Web/Client/Services/LocalStorageService.cs b/src/dotnet/QuantEngine.Web/Client/Services/LocalStorageService.cs deleted file mode 100644 index bd175f4d..00000000 --- a/src/dotnet/QuantEngine.Web/Client/Services/LocalStorageService.cs +++ /dev/null @@ -1,43 +0,0 @@ -using Microsoft.JSInterop; -using System.Text.Json; - -namespace QuantEngine.Web.Client.Services -{ - public class LocalStorageService - { - private readonly IJSRuntime _js; - - public LocalStorageService(IJSRuntime js) - { - _js = js; - } - - public async Task SetAsync(string key, T value) - { - var json = JsonSerializer.Serialize(value); - await _js.InvokeVoidAsync("localStorage.setItem", key, json); - } - - public async Task GetAsync(string key) - { - try - { - var json = await _js.InvokeAsync("localStorage.getItem", key); - if (string.IsNullOrEmpty(json)) - { - return default; - } - return JsonSerializer.Deserialize(json); - } - catch - { - return default; - } - } - - public async Task DeleteAsync(string key) - { - await _js.InvokeVoidAsync("localStorage.removeItem", key); - } - } -} diff --git a/src/dotnet/QuantEngine.Web/Client/Theme/AppTheme.cs b/src/dotnet/QuantEngine.Web/Client/Theme/AppTheme.cs deleted file mode 100644 index 17b7eeaa..00000000 --- a/src/dotnet/QuantEngine.Web/Client/Theme/AppTheme.cs +++ /dev/null @@ -1,158 +0,0 @@ -using MudBlazor; - -namespace QuantEngine.Web.Client.Theme; - -public static class AppTheme -{ - public static MudTheme LightTheme => new() - { - PaletteLight = new PaletteLight - { - Primary = "#3f51b5", - Secondary = "#f50057", - Success = "#4caf50", - Warning = "#ff9800", - Error = "#f44336", - Info = "#2196f3", - Dark = "#121212", - Background = "#fafafa", - Surface = "#ffffff", - TextPrimary = "#212121", - TextSecondary = "rgba(0,0,0,0.6)", - DrawerBackground = "#ffffff", - DrawerText = "#212121", - AppbarBackground = "#3f51b5", - AppbarText = "#ffffff", - ActionDefault = "#c0c0c0", - ActionDisabled = "#f5f5f5", - ActionDisabledBackground = "rgba(0,0,0,0.12)", - Divider = "#e0e0e0", - DividerLight = "#f5f5f5", - TableLines = "#e0e0e0", - LinesDefault = "#e0e0e0", - LinesInputs = "#bdbdbd", - TextDisabled = "rgba(0,0,0,0.38)" - }, - Typography = new Typography - { - Default = new DefaultTypography - { - FontFamily = new[] { "Roboto", "sans-serif" }, - FontSize = "1rem", - FontWeight = "400", - LineHeight = "1.5", - LetterSpacing = "0.5px" - }, - H1 = new H1Typography - { - FontSize = "6rem", - FontWeight = "300", - LineHeight = "1.167", - LetterSpacing = "-0.015625em" - }, - H2 = new H2Typography - { - FontSize = "3.75rem", - FontWeight = "300", - LineHeight = "1.2", - LetterSpacing = "-0.0083333333em" - }, - H3 = new H3Typography - { - FontSize = "3rem", - FontWeight = "400", - LineHeight = "1.167", - LetterSpacing = "0em" - }, - H4 = new H4Typography - { - FontSize = "2.125rem", - FontWeight = "500", - LineHeight = "1.235", - LetterSpacing = "0.0125em" - }, - H5 = new H5Typography - { - FontSize = "1.5rem", - FontWeight = "500", - LineHeight = "1.334", - LetterSpacing = "0em" - }, - H6 = new H6Typography - { - FontSize = "1.25rem", - FontWeight = "600", - LineHeight = "1.6", - LetterSpacing = "0.0125em" - }, - Body1 = new Body1Typography - { - FontSize = "1rem", - FontWeight = "500", - LineHeight = "1.5", - LetterSpacing = "0.03125em" - }, - Body2 = new Body2Typography - { - FontSize = "0.875rem", - FontWeight = "400", - LineHeight = "1.43", - LetterSpacing = "0.0178571429em" - }, - Button = new ButtonTypography - { - FontSize = "0.875rem", - FontWeight = "600", - LineHeight = "1.75", - LetterSpacing = "0.0892857143em" - }, - Caption = new CaptionTypography - { - FontSize = "0.75rem", - FontWeight = "400", - LineHeight = "1.66", - LetterSpacing = "0.0333333333em" - } - }, - LayoutProperties = new LayoutProperties - { - DefaultBorderRadius = "4px", - DrawerWidthLeft = "256px", - DrawerWidthRight = "256px", - AppbarHeight = "64px", - } - }; - - public static MudTheme DarkTheme => new() - { - PaletteDark = new PaletteDark - { - Primary = "#bb86fc", - Secondary = "#03dac6", - Success = "#4caf50", - Warning = "#ff9800", - Error = "#cf6679", - Info = "#2196f3", - Dark = "#121212", - Background = "#121212", - Surface = "#1e1e1e", - TextPrimary = "#ffffff", - TextSecondary = "rgba(255,255,255,0.7)", - DrawerBackground = "#1e1e1e", - DrawerText = "#ffffff", - AppbarBackground = "#1f1f1f", - AppbarText = "#ffffff", - ActionDefault = "#3f3f3f", - ActionDisabled = "#1e1e1e", - ActionDisabledBackground = "rgba(255,255,255,0.12)", - Divider = "#37474f", - DividerLight = "#2c3e50", - TableLines = "#37474f", - LinesDefault = "#37474f", - LinesInputs = "#555555", - TextDisabled = "rgba(255,255,255,0.38)" - }, - Typography = LightTheme.Typography, - LayoutProperties = LightTheme.LayoutProperties - }; -} diff --git a/src/dotnet/QuantEngine.Web/Client/_Imports.razor b/src/dotnet/QuantEngine.Web/Client/_Imports.razor deleted file mode 100644 index fe60c8a7..00000000 --- a/src/dotnet/QuantEngine.Web/Client/_Imports.razor +++ /dev/null @@ -1,16 +0,0 @@ -@using System.Net.Http -@using System.Net.Http.Json -@using Microsoft.AspNetCore.Components.Forms -@using Microsoft.AspNetCore.Components.Routing -@using Microsoft.AspNetCore.Components.Web -@using static Microsoft.AspNetCore.Components.Web.RenderMode -@using Microsoft.AspNetCore.Components.Web.Virtualization -@using Microsoft.JSInterop -@using MudBlazor -@using QuantEngine.Web.Client -@using QuantEngine.Web.Client.Pages -@using QuantEngine.Web.Client.Layout -@using QuantEngine.Web.Client.Infrastructure -@using QuantEngine.Web.Client.Services -@using Microsoft.AspNetCore.Components.Authorization -@using Microsoft.AspNetCore.Authorization diff --git a/src/dotnet/QuantEngine.Web/Client/app.css b/src/dotnet/QuantEngine.Web/Client/app.css deleted file mode 100644 index a743fccd..00000000 --- a/src/dotnet/QuantEngine.Web/Client/app.css +++ /dev/null @@ -1,297 +0,0 @@ -/* QuantEngine Global Styles */ - -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -html, body { - height: 100%; -} - -body { - font-family: 'Roboto', sans-serif; - font-size: 14px; - font-weight: 400; - line-height: 1.5; - color: var(--mud-palette-text-primary, #212121); - background-color: var(--mud-palette-background, #fafafa); - transition: background-color 0.3s ease, color 0.3s ease; -} - -#app { - display: flex; - flex-direction: column; - height: 100%; -} - -/* Scrollbar Styling */ -::-webkit-scrollbar { - width: 8px; - height: 8px; -} - -::-webkit-scrollbar-track { - background: var(--mud-palette-surface, #ffffff); -} - -::-webkit-scrollbar-thumb { - background: var(--mud-palette-action-default, #c0c0c0); - border-radius: 4px; -} - -::-webkit-scrollbar-thumb:hover { - background: var(--mud-palette-primary, #3f51b5); -} - -/* Text Utilities */ -.text-primary { - color: var(--mud-palette-primary, #3f51b5); -} - -.text-secondary { - color: var(--mud-palette-secondary, #f50057); -} - -.text-success { - color: var(--mud-palette-success, #4caf50); -} - -.text-warning { - color: var(--mud-palette-warning, #ff9800); -} - -.text-error { - color: var(--mud-palette-error, #f44336); -} - -.text-muted { - color: var(--mud-palette-text-secondary, rgba(0,0,0,0.6)); -} - -/* Spacing Utilities */ -.mt-1 { margin-top: 0.25rem; } -.mt-2 { margin-top: 0.5rem; } -.mt-3 { margin-top: 1rem; } -.mt-4 { margin-top: 1.5rem; } -.mt-5 { margin-top: 3rem; } - -.mb-1 { margin-bottom: 0.25rem; } -.mb-2 { margin-bottom: 0.5rem; } -.mb-3 { margin-bottom: 1rem; } -.mb-4 { margin-bottom: 1.5rem; } -.mb-5 { margin-bottom: 3rem; } - -.mx-auto { margin-left: auto; margin-right: auto; } -.my-auto { margin-top: auto; margin-bottom: auto; } - -.px-2 { padding-left: 0.5rem; padding-right: 0.5rem; } -.px-4 { padding-left: 1rem; padding-right: 1rem; } -.py-2 { padding-top: 0.5rem; padding-bottom: 0.5rem; } -.py-4 { padding-top: 1rem; padding-bottom: 1rem; } - -/* Flex Utilities */ -.d-flex { - display: flex; -} - -.flex-column { - flex-direction: column; -} - -.align-items-center { - align-items: center; -} - -.justify-content-center { - justify-content: center; -} - -.justify-content-between { - justify-content: space-between; -} - -/* Gap Utilities */ -.gap-1 { gap: 0.25rem; } -.gap-2 { gap: 0.5rem; } -.gap-3 { gap: 1rem; } -.gap-4 { gap: 1.5rem; } - -/* Loading Skeleton */ -.skeleton { - background: linear-gradient( - 90deg, - var(--mud-palette-surface, #fff) 0%, - var(--mud-palette-divider, #e0e0e0) 50%, - var(--mud-palette-surface, #fff) 100% - ); - background-size: 200% 100%; - animation: loading 1.5s infinite; -} - -@keyframes loading { - 0% { - background-position: 200% 0; - } - 100% { - background-position: -200% 0; - } -} - -/* MudBlazor Overrides */ -.mud-appbar { - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); -} - -.mud-drawer { - border-right: 1px solid var(--mud-palette-divider, #e0e0e0); -} - -.mud-drawer-content { - padding: 1rem; -} - -.mud-nav-link { - border-radius: 4px; - margin-bottom: 0.25rem; - transition: all 0.2s ease; -} - -.mud-nav-link:hover { - background-color: var(--mud-palette-action-default-hover, rgba(0, 0, 0, 0.04)); -} - -.mud-nav-link.mud-ripple-nav-link-active { - background-color: var(--mud-palette-primary-lighten, rgba(63, 81, 181, 0.1)); - color: var(--mud-palette-primary, #3f51b5); - font-weight: 600; -} - -.mud-card { - border: 1px solid var(--mud-palette-divider, #e0e0e0); - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); - transition: box-shadow 0.2s ease, transform 0.2s ease; -} - -.mud-card:hover { - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); - transform: translateY(-2px); -} - -.mud-button { - text-transform: none; - font-weight: 500; - padding: 0.5rem 1rem; - border-radius: 4px; -} - -.mud-button-root:disabled { - opacity: 0.6; -} - -/* Forms */ -.mud-input-control { - margin-bottom: 1rem; -} - -.mud-input-label { - font-weight: 500; -} - -.mud-input { - border-radius: 4px; -} - -.mud-input.mud-input-text { - background-color: var(--mud-palette-surface, #ffffff); -} - -/* Tables */ -.mud-table { - background-color: var(--mud-palette-surface, #ffffff); -} - -.mud-table-head { - background-color: var(--mud-palette-background, #fafafa); -} - -.mud-table-row:hover { - background-color: var(--mud-palette-action-default-hover, rgba(0, 0, 0, 0.04)); -} - -.mud-table-cell { - padding: 1rem; - border-color: var(--mud-palette-divider, #e0e0e0); -} - -/* Responsive */ -@media (max-width: 600px) { - body { - font-size: 13px; - } - - .mud-drawer { - width: 100% !important; - max-width: 90% !important; - } - - .mud-appbar { - height: 56px; - } - - .mud-table-cell { - padding: 0.75rem 0.5rem; - } -} - -/* Animation Classes */ -.fade-in { - animation: fadeIn 0.3s ease-in; -} - -@keyframes fadeIn { - from { - opacity: 0; - } - to { - opacity: 1; - } -} - -.slide-in { - animation: slideIn 0.3s ease-in; -} - -@keyframes slideIn { - from { - transform: translateY(10px); - opacity: 0; - } - to { - transform: translateY(0); - opacity: 1; - } -} - -/* Accessibility */ -@media (prefers-reduced-motion: reduce) { - * { - animation-duration: 0.01ms !important; - animation-iteration-count: 1 !important; - transition-duration: 0.01ms !important; - } -} - -/* Print Styles */ -@media print { - .mud-appbar, - .mud-drawer, - .no-print { - display: none !important; - } - - body { - background: white; - } -} diff --git a/src/dotnet/QuantEngine.Web/QuantEngine.Web.csproj b/src/dotnet/QuantEngine.Web/QuantEngine.Web.csproj index 3b1fba30..7cf44f87 100644 --- a/src/dotnet/QuantEngine.Web/QuantEngine.Web.csproj +++ b/src/dotnet/QuantEngine.Web/QuantEngine.Web.csproj @@ -18,25 +18,10 @@ - - - - - - - - - - - - - - net10.0 enable enable - true diff --git a/src/dotnet/QuantEngine.Web/logs/quantengine-20260711.log b/src/dotnet/QuantEngine.Web/logs/quantengine-20260711.log deleted file mode 100644 index 54c4f439..00000000 --- a/src/dotnet/QuantEngine.Web/logs/quantengine-20260711.log +++ /dev/null @@ -1,18 +0,0 @@ -2026-07-11 17:02:14.217 +09:00 [FTL] Application terminated unexpectedly -System.AggregateException: Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: QuantEngine.Core.Interfaces.IKisApiClient Lifetime: Scoped ImplementationType: QuantEngine.Infrastructure.Services.KisApiClient': Unable to resolve service for type 'System.Net.Http.HttpClient' while attempting to activate 'QuantEngine.Infrastructure.Services.KisApiClient'.) - ---> System.InvalidOperationException: Error while validating the service descriptor 'ServiceType: QuantEngine.Core.Interfaces.IKisApiClient Lifetime: Scoped ImplementationType: QuantEngine.Infrastructure.Services.KisApiClient': Unable to resolve service for type 'System.Net.Http.HttpClient' while attempting to activate 'QuantEngine.Infrastructure.Services.KisApiClient'. - ---> System.InvalidOperationException: Unable to resolve service for type 'System.Net.Http.HttpClient' while attempting to activate 'QuantEngine.Infrastructure.Services.KisApiClient'. - at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateArgumentCallSites(ServiceIdentifier serviceIdentifier, Type implementationType, CallSiteChain callSiteChain, ParameterInfo[] parameters, Boolean throwIfCallSiteNotFound) - at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateConstructorCallSite(ResultCache lifetime, ServiceIdentifier serviceIdentifier, Type implementationType, CallSiteChain callSiteChain) - at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateExact(ServiceDescriptor descriptor, ServiceIdentifier serviceIdentifier, CallSiteChain callSiteChain, Int32 slot) - at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.GetCallSite(ServiceDescriptor serviceDescriptor, CallSiteChain callSiteChain) - at Microsoft.Extensions.DependencyInjection.ServiceProvider.ValidateService(ServiceDescriptor descriptor) - --- End of inner exception stack trace --- - at Microsoft.Extensions.DependencyInjection.ServiceProvider.ValidateService(ServiceDescriptor descriptor) - at Microsoft.Extensions.DependencyInjection.ServiceProvider..ctor(ICollection`1 serviceDescriptors, ServiceProviderOptions options) - --- End of inner exception stack trace --- - at Microsoft.Extensions.DependencyInjection.ServiceProvider..ctor(ICollection`1 serviceDescriptors, ServiceProviderOptions options) - at Microsoft.Extensions.DependencyInjection.ServiceCollectionContainerBuilderExtensions.BuildServiceProvider(IServiceCollection services, ServiceProviderOptions options) - at Microsoft.Extensions.Hosting.HostApplicationBuilder.Build() - at Microsoft.AspNetCore.Builder.WebApplicationBuilder.Build() - at Program.
$(String[] args) in C:\Temp\data_feed\src\dotnet\QuantEngine.Web\Program.cs:line 99 diff --git a/src/dotnet/QuantEngine.Web/logs/quantengine-20260711_001.log b/src/dotnet/QuantEngine.Web/logs/quantengine-20260711_001.log new file mode 100644 index 00000000..c0c0a307 --- /dev/null +++ b/src/dotnet/QuantEngine.Web/logs/quantengine-20260711_001.log @@ -0,0 +1,381 @@ +2026-07-11 18:41:46.431 +09:00 [INF] Registered 10 endpoints in 164 milliseconds. +2026-07-11 18:41:46.473 +09:00 [INF] 🔄 Starting database migration with DbUp... +2026-07-11 18:41:50.463 +09:00 [ERR] ❌ Database migration failed: 28P01: password authentication failed for user "quantengine_app" +2026-07-11 18:41:50.464 +09:00 [ERR] ❌ Database migration failed +System.InvalidOperationException: Database migration failed: 28P01: password authentication failed for user "quantengine_app" + at QuantEngine.Infrastructure.Data.DbMigrator.Migrate() in C:\Temp\data_feed\src\dotnet\QuantEngine.Infrastructure\Data\DbMigrator.cs:line 39 +2026-07-11 18:41:50.476 +09:00 [WRN] Database initialization warning (development only): Database migration failed: 28P01: password authentication failed for user "quantengine_app" +2026-07-11 18:41:50.498 +09:00 [INF] User profile is available. Using 'C:\Users\kjh20\AppData\Local\ASP.NET\DataProtection-Keys' as key repository and Windows DPAPI to encrypt keys at rest. +2026-07-11 18:42:27.125 +09:00 [INF] Registered 10 endpoints in 178 milliseconds. +2026-07-11 18:42:27.164 +09:00 [INF] 🔄 Starting database migration with DbUp... +2026-07-11 18:42:31.234 +09:00 [ERR] ❌ Database migration failed: 28P01: password authentication failed for user "quantengine_app" +2026-07-11 18:42:31.234 +09:00 [ERR] ❌ Database migration failed +System.InvalidOperationException: Database migration failed: 28P01: password authentication failed for user "quantengine_app" + at QuantEngine.Infrastructure.Data.DbMigrator.Migrate() in C:\Temp\data_feed\src\dotnet\QuantEngine.Infrastructure\Data\DbMigrator.cs:line 39 +2026-07-11 18:42:31.240 +09:00 [WRN] Database initialization warning (development only): Database migration failed: 28P01: password authentication failed for user "quantengine_app" +2026-07-11 18:42:31.281 +09:00 [INF] User profile is available. Using 'C:\Users\kjh20\AppData\Local\ASP.NET\DataProtection-Keys' as key repository and Windows DPAPI to encrypt keys at rest. +2026-07-11 18:42:35.322 +09:00 [WRN] Hangfire setup failed: Cannot resolve scoped service 'QuantEngine.Web.Services.SchedulerService' from root provider. +2026-07-11 18:42:35.536 +09:00 [ERR] Hosting failed to start +System.IO.IOException: Failed to bind to address http://127.0.0.1:5265: address already in use. + ---> Microsoft.AspNetCore.Connections.AddressInUseException: 각 소켓 주소(프로토콜/네트워크 주소/포트)는 하나만 사용할 수 있습니다. + ---> System.Net.Sockets.SocketException (10048): 각 소켓 주소(프로토콜/네트워크 주소/포트)는 하나만 사용할 수 있습니다. + at System.Net.Sockets.Socket.UpdateStatusAfterSocketErrorAndThrowException(SocketError error, Boolean disconnectOnFailure, String callerName) + at System.Net.Sockets.Socket.DoBind(EndPoint endPointSnapshot, SocketAddress socketAddress) + at System.Net.Sockets.Socket.Bind(EndPoint localEP) + at Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketTransportOptions.CreateDefaultBoundListenSocket(EndPoint endpoint) + at Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketConnectionListener.Bind() + --- End of inner exception stack trace --- + at Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketConnectionListener.Bind() + at Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketTransportFactory.BindAsync(EndPoint endpoint, CancellationToken cancellationToken) + at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.TransportManager.BindAsync(EndPoint endPoint, ConnectionDelegate connectionDelegate, EndpointConfig endpointConfig, CancellationToken cancellationToken) + at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.<>c__DisplayClass28_0`1.<g__OnBind|0>d.MoveNext() +--- End of stack trace from previous location --- + at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.BindEndpointAsync(ListenOptions endpoint, AddressBindContext context, CancellationToken cancellationToken) + --- End of inner exception stack trace --- + at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.BindEndpointAsync(ListenOptions endpoint, AddressBindContext context, CancellationToken cancellationToken) + at Microsoft.AspNetCore.Server.Kestrel.Core.LocalhostListenOptions.BindAsync(AddressBindContext context, CancellationToken cancellationToken) + at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.AddressesStrategy.BindAsync(AddressBindContext context, CancellationToken cancellationToken) + at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.BindAsync(CancellationToken cancellationToken) + at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.StartAsync[TContext](IHttpApplication`1 application, CancellationToken cancellationToken) + at Microsoft.AspNetCore.Hosting.GenericWebHostService.StartAsync(CancellationToken cancellationToken) + at Microsoft.Extensions.Hosting.Internal.Host.b__14_1(IHostedService service, CancellationToken token) + at Microsoft.Extensions.Hosting.Internal.Host.ForeachService[T](IEnumerable`1 services, CancellationToken token, Boolean concurrent, Boolean abortOnFirstException, List`1 exceptions, Func`3 operation) +2026-07-11 18:42:35.543 +09:00 [FTL] Application terminated unexpectedly +System.IO.IOException: Failed to bind to address http://127.0.0.1:5265: address already in use. + ---> Microsoft.AspNetCore.Connections.AddressInUseException: 각 소켓 주소(프로토콜/네트워크 주소/포트)는 하나만 사용할 수 있습니다. + ---> System.Net.Sockets.SocketException (10048): 각 소켓 주소(프로토콜/네트워크 주소/포트)는 하나만 사용할 수 있습니다. + at System.Net.Sockets.Socket.UpdateStatusAfterSocketErrorAndThrowException(SocketError error, Boolean disconnectOnFailure, String callerName) + at System.Net.Sockets.Socket.DoBind(EndPoint endPointSnapshot, SocketAddress socketAddress) + at System.Net.Sockets.Socket.Bind(EndPoint localEP) + at Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketTransportOptions.CreateDefaultBoundListenSocket(EndPoint endpoint) + at Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketConnectionListener.Bind() + --- End of inner exception stack trace --- + at Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketConnectionListener.Bind() + at Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketTransportFactory.BindAsync(EndPoint endpoint, CancellationToken cancellationToken) + at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.TransportManager.BindAsync(EndPoint endPoint, ConnectionDelegate connectionDelegate, EndpointConfig endpointConfig, CancellationToken cancellationToken) + at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.<>c__DisplayClass28_0`1.<g__OnBind|0>d.MoveNext() +--- End of stack trace from previous location --- + at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.BindEndpointAsync(ListenOptions endpoint, AddressBindContext context, CancellationToken cancellationToken) + --- End of inner exception stack trace --- + at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.BindEndpointAsync(ListenOptions endpoint, AddressBindContext context, CancellationToken cancellationToken) + at Microsoft.AspNetCore.Server.Kestrel.Core.LocalhostListenOptions.BindAsync(AddressBindContext context, CancellationToken cancellationToken) + at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.AddressesStrategy.BindAsync(AddressBindContext context, CancellationToken cancellationToken) + at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.BindAsync(CancellationToken cancellationToken) + at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.StartAsync[TContext](IHttpApplication`1 application, CancellationToken cancellationToken) + at Microsoft.AspNetCore.Hosting.GenericWebHostService.StartAsync(CancellationToken cancellationToken) + at Microsoft.Extensions.Hosting.Internal.Host.b__14_1(IHostedService service, CancellationToken token) + at Microsoft.Extensions.Hosting.Internal.Host.ForeachService[T](IEnumerable`1 services, CancellationToken token, Boolean concurrent, Boolean abortOnFirstException, List`1 exceptions, Func`3 operation) + at Microsoft.Extensions.Hosting.Internal.Host.StartAsync(CancellationToken cancellationToken) + at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token) + at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token) + at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.Run(IHost host) + at Program.
$(String[] args) in C:\Temp\data_feed\src\dotnet\QuantEngine.Web\Program.cs:line 172 +2026-07-11 18:47:26.979 +09:00 [INF] Registered 10 endpoints in 173 milliseconds. +2026-07-11 18:47:27.018 +09:00 [INF] 🔄 Starting database migration with DbUp... +2026-07-11 18:47:31.144 +09:00 [ERR] ❌ Database migration failed: 28P01: password authentication failed for user "quantengine_app" +2026-07-11 18:47:31.144 +09:00 [ERR] ❌ Database migration failed +System.InvalidOperationException: Database migration failed: 28P01: password authentication failed for user "quantengine_app" + at QuantEngine.Infrastructure.Data.DbMigrator.Migrate() in C:\Temp\data_feed\src\dotnet\QuantEngine.Infrastructure\Data\DbMigrator.cs:line 39 +2026-07-11 18:47:31.149 +09:00 [WRN] Database initialization warning (development only): Database migration failed: 28P01: password authentication failed for user "quantengine_app" +2026-07-11 18:47:31.177 +09:00 [INF] User profile is available. Using 'C:\Users\kjh20\AppData\Local\ASP.NET\DataProtection-Keys' as key repository and Windows DPAPI to encrypt keys at rest. +2026-07-11 18:47:58.996 +09:00 [INF] Registered 10 endpoints in 163 milliseconds. +2026-07-11 18:47:59.034 +09:00 [INF] 🔄 Starting database migration with DbUp... +2026-07-11 18:48:03.135 +09:00 [ERR] ❌ Database migration failed: 28P01: password authentication failed for user "quantengine_app" +2026-07-11 18:48:03.136 +09:00 [ERR] ❌ Database migration failed +System.InvalidOperationException: Database migration failed: 28P01: password authentication failed for user "quantengine_app" + at QuantEngine.Infrastructure.Data.DbMigrator.Migrate() in C:\Temp\data_feed\src\dotnet\QuantEngine.Infrastructure\Data\DbMigrator.cs:line 39 +2026-07-11 18:48:03.142 +09:00 [WRN] Database initialization warning (development only): Database migration failed: 28P01: password authentication failed for user "quantengine_app" +2026-07-11 18:48:03.166 +09:00 [INF] User profile is available. Using 'C:\Users\kjh20\AppData\Local\ASP.NET\DataProtection-Keys' as key repository and Windows DPAPI to encrypt keys at rest. +2026-07-11 18:48:07.181 +09:00 [WRN] Hangfire setup failed: Cannot resolve scoped service 'QuantEngine.Web.Services.SchedulerService' from root provider. +2026-07-11 18:48:07.453 +09:00 [ERR] Hosting failed to start +System.IO.IOException: Failed to bind to address http://127.0.0.1:5265: address already in use. + ---> Microsoft.AspNetCore.Connections.AddressInUseException: 각 소켓 주소(프로토콜/네트워크 주소/포트)는 하나만 사용할 수 있습니다. + ---> System.Net.Sockets.SocketException (10048): 각 소켓 주소(프로토콜/네트워크 주소/포트)는 하나만 사용할 수 있습니다. + at System.Net.Sockets.Socket.UpdateStatusAfterSocketErrorAndThrowException(SocketError error, Boolean disconnectOnFailure, String callerName) + at System.Net.Sockets.Socket.DoBind(EndPoint endPointSnapshot, SocketAddress socketAddress) + at System.Net.Sockets.Socket.Bind(EndPoint localEP) + at Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketTransportOptions.CreateDefaultBoundListenSocket(EndPoint endpoint) + at Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketConnectionListener.Bind() + --- End of inner exception stack trace --- + at Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketConnectionListener.Bind() + at Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketTransportFactory.BindAsync(EndPoint endpoint, CancellationToken cancellationToken) + at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.TransportManager.BindAsync(EndPoint endPoint, ConnectionDelegate connectionDelegate, EndpointConfig endpointConfig, CancellationToken cancellationToken) + at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.<>c__DisplayClass28_0`1.<g__OnBind|0>d.MoveNext() +--- End of stack trace from previous location --- + at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.BindEndpointAsync(ListenOptions endpoint, AddressBindContext context, CancellationToken cancellationToken) + --- End of inner exception stack trace --- + at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.BindEndpointAsync(ListenOptions endpoint, AddressBindContext context, CancellationToken cancellationToken) + at Microsoft.AspNetCore.Server.Kestrel.Core.LocalhostListenOptions.BindAsync(AddressBindContext context, CancellationToken cancellationToken) + at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.AddressesStrategy.BindAsync(AddressBindContext context, CancellationToken cancellationToken) + at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.BindAsync(CancellationToken cancellationToken) + at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.StartAsync[TContext](IHttpApplication`1 application, CancellationToken cancellationToken) + at Microsoft.AspNetCore.Hosting.GenericWebHostService.StartAsync(CancellationToken cancellationToken) + at Microsoft.Extensions.Hosting.Internal.Host.b__14_1(IHostedService service, CancellationToken token) + at Microsoft.Extensions.Hosting.Internal.Host.ForeachService[T](IEnumerable`1 services, CancellationToken token, Boolean concurrent, Boolean abortOnFirstException, List`1 exceptions, Func`3 operation) +2026-07-11 18:48:07.460 +09:00 [FTL] Application terminated unexpectedly +System.IO.IOException: Failed to bind to address http://127.0.0.1:5265: address already in use. + ---> Microsoft.AspNetCore.Connections.AddressInUseException: 각 소켓 주소(프로토콜/네트워크 주소/포트)는 하나만 사용할 수 있습니다. + ---> System.Net.Sockets.SocketException (10048): 각 소켓 주소(프로토콜/네트워크 주소/포트)는 하나만 사용할 수 있습니다. + at System.Net.Sockets.Socket.UpdateStatusAfterSocketErrorAndThrowException(SocketError error, Boolean disconnectOnFailure, String callerName) + at System.Net.Sockets.Socket.DoBind(EndPoint endPointSnapshot, SocketAddress socketAddress) + at System.Net.Sockets.Socket.Bind(EndPoint localEP) + at Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketTransportOptions.CreateDefaultBoundListenSocket(EndPoint endpoint) + at Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketConnectionListener.Bind() + --- End of inner exception stack trace --- + at Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketConnectionListener.Bind() + at Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketTransportFactory.BindAsync(EndPoint endpoint, CancellationToken cancellationToken) + at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.TransportManager.BindAsync(EndPoint endPoint, ConnectionDelegate connectionDelegate, EndpointConfig endpointConfig, CancellationToken cancellationToken) + at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.<>c__DisplayClass28_0`1.<g__OnBind|0>d.MoveNext() +--- End of stack trace from previous location --- + at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.BindEndpointAsync(ListenOptions endpoint, AddressBindContext context, CancellationToken cancellationToken) + --- End of inner exception stack trace --- + at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.BindEndpointAsync(ListenOptions endpoint, AddressBindContext context, CancellationToken cancellationToken) + at Microsoft.AspNetCore.Server.Kestrel.Core.LocalhostListenOptions.BindAsync(AddressBindContext context, CancellationToken cancellationToken) + at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.AddressesStrategy.BindAsync(AddressBindContext context, CancellationToken cancellationToken) + at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.BindAsync(CancellationToken cancellationToken) + at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.StartAsync[TContext](IHttpApplication`1 application, CancellationToken cancellationToken) + at Microsoft.AspNetCore.Hosting.GenericWebHostService.StartAsync(CancellationToken cancellationToken) + at Microsoft.Extensions.Hosting.Internal.Host.b__14_1(IHostedService service, CancellationToken token) + at Microsoft.Extensions.Hosting.Internal.Host.ForeachService[T](IEnumerable`1 services, CancellationToken token, Boolean concurrent, Boolean abortOnFirstException, List`1 exceptions, Func`3 operation) + at Microsoft.Extensions.Hosting.Internal.Host.StartAsync(CancellationToken cancellationToken) + at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token) + at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token) + at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.Run(IHost host) + at Program.
$(String[] args) in C:\Temp\data_feed\src\dotnet\QuantEngine.Web\Program.cs:line 172 +2026-07-11 18:49:42.328 +09:00 [INF] Registered 10 endpoints in 160 milliseconds. +2026-07-11 18:49:42.368 +09:00 [INF] 🔄 Starting database migration with DbUp... +2026-07-11 18:49:46.632 +09:00 [ERR] ❌ Database migration failed: 28P01: password authentication failed for user "quantengine_app" +2026-07-11 18:49:46.632 +09:00 [ERR] ❌ Database migration failed +System.InvalidOperationException: Database migration failed: 28P01: password authentication failed for user "quantengine_app" + at QuantEngine.Infrastructure.Data.DbMigrator.Migrate() in C:\Temp\data_feed\src\dotnet\QuantEngine.Infrastructure\Data\DbMigrator.cs:line 39 +2026-07-11 18:49:46.635 +09:00 [WRN] Database initialization warning (development only): Database migration failed: 28P01: password authentication failed for user "quantengine_app" +2026-07-11 18:49:46.655 +09:00 [INF] User profile is available. Using 'C:\Users\kjh20\AppData\Local\ASP.NET\DataProtection-Keys' as key repository and Windows DPAPI to encrypt keys at rest. +2026-07-11 18:49:50.782 +09:00 [WRN] Hangfire setup failed: Cannot resolve scoped service 'QuantEngine.Web.Services.SchedulerService' from root provider. +2026-07-11 18:49:50.940 +09:00 [INF] Now listening on: http://localhost:5265 +2026-07-11 18:49:50.946 +09:00 [INF] Starting Hangfire Server using job storage: 'Hangfire.MemoryStorage.MemoryStorage' +2026-07-11 18:49:50.946 +09:00 [INF] Using the following options for Hangfire Server: + Worker count: 32 + Listening queues: 'default' + Shutdown timeout: 00:00:15 + Schedule polling interval: 00:00:15 +2026-07-11 18:49:50.966 +09:00 [INF] Application started. Press Ctrl+C to shut down. +2026-07-11 18:49:50.966 +09:00 [INF] Hosting environment: Development +2026-07-11 18:49:50.966 +09:00 [INF] Content root path: C:\Temp\data_feed\src\dotnet\QuantEngine.Web +2026-07-11 18:49:51.046 +09:00 [INF] Server kimjaehyun-note:18540:24fc9869 successfully announced in 77.2082 ms +2026-07-11 18:49:51.050 +09:00 [INF] Server kimjaehyun-note:18540:24fc9869 is starting the registered dispatchers: ServerWatchdog, ServerJobCancellationWatcher, ExpirationManager, CountersAggregator, Worker, DelayedJobScheduler, RecurringJobScheduler... +2026-07-11 18:49:51.076 +09:00 [INF] Server kimjaehyun-note:18540:24fc9869 all the dispatchers started +2026-07-11 18:50:18.938 +09:00 [INF] Registered 10 endpoints in 161 milliseconds. +2026-07-11 18:50:18.977 +09:00 [INF] 🔄 Starting database migration with DbUp... +2026-07-11 18:50:23.249 +09:00 [ERR] ❌ Database migration failed: 28P01: password authentication failed for user "quantengine_app" +2026-07-11 18:50:23.250 +09:00 [ERR] ❌ Database migration failed +System.InvalidOperationException: Database migration failed: 28P01: password authentication failed for user "quantengine_app" + at QuantEngine.Infrastructure.Data.DbMigrator.Migrate() in C:\Temp\data_feed\src\dotnet\QuantEngine.Infrastructure\Data\DbMigrator.cs:line 39 +2026-07-11 18:50:23.256 +09:00 [WRN] Database initialization warning (development only): Database migration failed: 28P01: password authentication failed for user "quantengine_app" +2026-07-11 18:50:23.283 +09:00 [INF] User profile is available. Using 'C:\Users\kjh20\AppData\Local\ASP.NET\DataProtection-Keys' as key repository and Windows DPAPI to encrypt keys at rest. +2026-07-11 18:50:27.388 +09:00 [WRN] Hangfire setup failed: Cannot resolve scoped service 'QuantEngine.Web.Services.SchedulerService' from root provider. +2026-07-11 18:50:27.569 +09:00 [INF] Now listening on: http://localhost:5265 +2026-07-11 18:50:27.577 +09:00 [INF] Starting Hangfire Server using job storage: 'Hangfire.MemoryStorage.MemoryStorage' +2026-07-11 18:50:27.577 +09:00 [INF] Using the following options for Hangfire Server: + Worker count: 32 + Listening queues: 'default' + Shutdown timeout: 00:00:15 + Schedule polling interval: 00:00:15 +2026-07-11 18:50:27.589 +09:00 [INF] Application started. Press Ctrl+C to shut down. +2026-07-11 18:50:27.590 +09:00 [INF] Hosting environment: Development +2026-07-11 18:50:27.590 +09:00 [INF] Content root path: C:\Temp\data_feed\src\dotnet\QuantEngine.Web +2026-07-11 18:50:27.646 +09:00 [INF] Server kimjaehyun-note:25888:2b65733c successfully announced in 54.5404 ms +2026-07-11 18:50:27.648 +09:00 [INF] Server kimjaehyun-note:25888:2b65733c is starting the registered dispatchers: ServerWatchdog, ServerJobCancellationWatcher, ExpirationManager, CountersAggregator, Worker, DelayedJobScheduler, RecurringJobScheduler... +2026-07-11 18:50:27.667 +09:00 [INF] Server kimjaehyun-note:25888:2b65733c all the dispatchers started +2026-07-11 18:50:40.967 +09:00 [INF] Request starting HTTP/1.1 GET http://localhost:5265/Account/Login - null null +2026-07-11 18:50:40.972 +09:00 [WRN] Failed to determine the https port for redirect. +2026-07-11 18:50:41.007 +09:00 [INF] Executing endpoint '/Account/Login' +2026-07-11 18:50:41.023 +09:00 [INF] Route matched with {page = "/Account/Login"}. Executing page /Account/Login +2026-07-11 18:50:41.030 +09:00 [INF] Executing handler method QuantEngine.Web.Pages.Account.LoginModel.OnGet - ModelState is "Valid" +2026-07-11 18:50:41.031 +09:00 [INF] Executed handler method OnGet, returned result . +2026-07-11 18:50:41.033 +09:00 [INF] Executing an implicit handler method - ModelState is "Valid" +2026-07-11 18:50:41.033 +09:00 [INF] Executed an implicit handler method, returned result Microsoft.AspNetCore.Mvc.RazorPages.PageResult. +2026-07-11 18:50:41.058 +09:00 [INF] Executed page /Account/Login in 32.7802ms +2026-07-11 18:50:41.059 +09:00 [INF] Executed endpoint '/Account/Login' +2026-07-11 18:50:41.060 +09:00 [INF] HTTP GET /Account/Login responded 200 in 88.7566 ms +2026-07-11 18:50:41.062 +09:00 [INF] Request finished HTTP/1.1 GET http://localhost:5265/Account/Login - 200 null text/html; charset=utf-8 94.7893ms +2026-07-11 18:50:41.174 +09:00 [INF] Request starting HTTP/1.1 GET http://localhost:5265/Admin/Dashboard - null null +2026-07-11 18:50:41.178 +09:00 [ERR] HTTP GET /Admin/Dashboard responded 500 in 4.0554 ms +System.InvalidOperationException: The AuthorizationPolicy named: 'AdminCookie' was not found. + at Microsoft.AspNetCore.Authorization.AuthorizationPolicy.CombineAsync(IAuthorizationPolicyProvider policyProvider, IEnumerable`1 authorizeData, IEnumerable`1 policies) + at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context) + at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context) + at Serilog.AspNetCore.RequestLoggingMiddleware.Invoke(HttpContext httpContext) +2026-07-11 18:50:41.181 +09:00 [ERR] An unhandled exception has occurred while executing the request. +System.InvalidOperationException: The AuthorizationPolicy named: 'AdminCookie' was not found. + at Microsoft.AspNetCore.Authorization.AuthorizationPolicy.CombineAsync(IAuthorizationPolicyProvider policyProvider, IEnumerable`1 authorizeData, IEnumerable`1 policies) + at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context) + at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context) + at Serilog.AspNetCore.RequestLoggingMiddleware.Invoke(HttpContext httpContext) + at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context) +2026-07-11 18:50:41.185 +09:00 [INF] Request finished HTTP/1.1 GET http://localhost:5265/Admin/Dashboard - 500 null text/plain; charset=utf-8 12.0749ms +2026-07-11 18:50:41.298 +09:00 [INF] Request starting HTTP/1.1 GET http://localhost:5265/Account/Login - null null +2026-07-11 18:50:41.303 +09:00 [INF] Executing endpoint '/Account/Login' +2026-07-11 18:50:41.303 +09:00 [INF] Route matched with {page = "/Account/Login"}. Executing page /Account/Login +2026-07-11 18:50:41.304 +09:00 [INF] Executing handler method QuantEngine.Web.Pages.Account.LoginModel.OnGet - ModelState is "Valid" +2026-07-11 18:50:41.304 +09:00 [INF] Executed handler method OnGet, returned result . +2026-07-11 18:50:41.304 +09:00 [INF] Executing an implicit handler method - ModelState is "Valid" +2026-07-11 18:50:41.304 +09:00 [INF] Executed an implicit handler method, returned result Microsoft.AspNetCore.Mvc.RazorPages.PageResult. +2026-07-11 18:50:41.310 +09:00 [INF] Executed page /Account/Login in 6.8704ms +2026-07-11 18:50:41.310 +09:00 [INF] Executed endpoint '/Account/Login' +2026-07-11 18:50:41.310 +09:00 [INF] HTTP GET /Account/Login responded 200 in 12.5730 ms +2026-07-11 18:50:41.311 +09:00 [INF] Request finished HTTP/1.1 GET http://localhost:5265/Account/Login - 200 null text/html; charset=utf-8 12.9475ms +2026-07-11 18:50:45.183 +09:00 [INF] Request starting HTTP/1.1 GET http://localhost:5265/Admin/Dashboard - null null +2026-07-11 18:50:45.185 +09:00 [ERR] HTTP GET /Admin/Dashboard responded 500 in 1.8220 ms +System.InvalidOperationException: The AuthorizationPolicy named: 'AdminCookie' was not found. + at Microsoft.AspNetCore.Authorization.AuthorizationPolicy.CombineAsync(IAuthorizationPolicyProvider policyProvider, IEnumerable`1 authorizeData, IEnumerable`1 policies) + at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context) + at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context) + at Serilog.AspNetCore.RequestLoggingMiddleware.Invoke(HttpContext httpContext) +2026-07-11 18:50:45.186 +09:00 [ERR] An unhandled exception has occurred while executing the request. +System.InvalidOperationException: The AuthorizationPolicy named: 'AdminCookie' was not found. + at Microsoft.AspNetCore.Authorization.AuthorizationPolicy.CombineAsync(IAuthorizationPolicyProvider policyProvider, IEnumerable`1 authorizeData, IEnumerable`1 policies) + at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context) + at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context) + at Serilog.AspNetCore.RequestLoggingMiddleware.Invoke(HttpContext httpContext) + at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context) +2026-07-11 18:50:45.186 +09:00 [INF] Request finished HTTP/1.1 GET http://localhost:5265/Admin/Dashboard - 500 null text/plain; charset=utf-8 2.8989ms +2026-07-11 18:51:53.325 +09:00 [INF] Registered 10 endpoints in 159 milliseconds. +2026-07-11 18:51:53.369 +09:00 [INF] 🔄 Starting database migration with DbUp... +2026-07-11 18:51:57.672 +09:00 [ERR] ❌ Database migration failed: 28P01: password authentication failed for user "quantengine_app" +2026-07-11 18:51:57.672 +09:00 [ERR] ❌ Database migration failed +System.InvalidOperationException: Database migration failed: 28P01: password authentication failed for user "quantengine_app" + at QuantEngine.Infrastructure.Data.DbMigrator.Migrate() in C:\Temp\data_feed\src\dotnet\QuantEngine.Infrastructure\Data\DbMigrator.cs:line 39 +2026-07-11 18:51:57.699 +09:00 [WRN] Database initialization warning (development only): Database migration failed: 28P01: password authentication failed for user "quantengine_app" +2026-07-11 18:51:57.728 +09:00 [INF] User profile is available. Using 'C:\Users\kjh20\AppData\Local\ASP.NET\DataProtection-Keys' as key repository and Windows DPAPI to encrypt keys at rest. +2026-07-11 18:52:01.841 +09:00 [WRN] Hangfire setup failed: Cannot resolve scoped service 'QuantEngine.Web.Services.SchedulerService' from root provider. +2026-07-11 18:52:02.012 +09:00 [INF] Now listening on: http://localhost:5265 +2026-07-11 18:52:02.023 +09:00 [INF] Starting Hangfire Server using job storage: 'Hangfire.MemoryStorage.MemoryStorage' +2026-07-11 18:52:02.024 +09:00 [INF] Using the following options for Hangfire Server: + Worker count: 32 + Listening queues: 'default' + Shutdown timeout: 00:00:15 + Schedule polling interval: 00:00:15 +2026-07-11 18:52:02.041 +09:00 [INF] Application started. Press Ctrl+C to shut down. +2026-07-11 18:52:02.041 +09:00 [INF] Hosting environment: Development +2026-07-11 18:52:02.041 +09:00 [INF] Content root path: C:\Temp\data_feed\src\dotnet\QuantEngine.Web +2026-07-11 18:52:02.106 +09:00 [INF] Server kimjaehyun-note:28380:a220bb7a successfully announced in 61.5885 ms +2026-07-11 18:52:02.108 +09:00 [INF] Server kimjaehyun-note:28380:a220bb7a is starting the registered dispatchers: ServerWatchdog, ServerJobCancellationWatcher, ExpirationManager, CountersAggregator, Worker, DelayedJobScheduler, RecurringJobScheduler... +2026-07-11 18:52:02.115 +09:00 [INF] Server kimjaehyun-note:28380:a220bb7a all the dispatchers started +2026-07-11 18:52:13.775 +09:00 [INF] Request starting HTTP/1.1 GET http://localhost:5265/Account/Login - null null +2026-07-11 18:52:13.780 +09:00 [WRN] Failed to determine the https port for redirect. +2026-07-11 18:52:13.833 +09:00 [INF] Executing endpoint '/Account/Login' +2026-07-11 18:52:13.849 +09:00 [INF] Route matched with {page = "/Account/Login"}. Executing page /Account/Login +2026-07-11 18:52:13.855 +09:00 [INF] Executing handler method QuantEngine.Web.Pages.Account.LoginModel.OnGet - ModelState is "Valid" +2026-07-11 18:52:13.856 +09:00 [INF] Executed handler method OnGet, returned result . +2026-07-11 18:52:13.858 +09:00 [INF] Executing an implicit handler method - ModelState is "Valid" +2026-07-11 18:52:13.859 +09:00 [INF] Executed an implicit handler method, returned result Microsoft.AspNetCore.Mvc.RazorPages.PageResult. +2026-07-11 18:52:13.883 +09:00 [INF] Executed page /Account/Login in 32.0627ms +2026-07-11 18:52:13.884 +09:00 [INF] Executed endpoint '/Account/Login' +2026-07-11 18:52:13.885 +09:00 [INF] HTTP GET /Account/Login responded 200 in 106.3802 ms +2026-07-11 18:52:13.887 +09:00 [INF] Request finished HTTP/1.1 GET http://localhost:5265/Account/Login - 200 null text/html; charset=utf-8 112.9483ms +2026-07-11 18:52:14.003 +09:00 [INF] Request starting HTTP/1.1 GET http://localhost:5265/Admin/Dashboard - null null +2026-07-11 18:52:14.011 +09:00 [INF] Authorization failed. These requirements were not met: +DenyAnonymousAuthorizationRequirement: Requires an authenticated user. +2026-07-11 18:52:14.013 +09:00 [INF] AuthenticationScheme: AdminCookie was challenged. +2026-07-11 18:52:14.013 +09:00 [INF] HTTP GET /Admin/Dashboard responded 302 in 10.4176 ms +2026-07-11 18:52:14.014 +09:00 [INF] Request finished HTTP/1.1 GET http://localhost:5265/Admin/Dashboard - 302 0 null 11.3635ms +2026-07-11 18:52:14.120 +09:00 [INF] Request starting HTTP/1.1 GET http://localhost:5265/Admin/Dashboard - null null +2026-07-11 18:52:14.124 +09:00 [INF] Authorization failed. These requirements were not met: +DenyAnonymousAuthorizationRequirement: Requires an authenticated user. +2026-07-11 18:52:14.124 +09:00 [INF] AuthenticationScheme: AdminCookie was challenged. +2026-07-11 18:52:14.125 +09:00 [INF] HTTP GET /Admin/Dashboard responded 302 in 4.4516 ms +2026-07-11 18:52:14.125 +09:00 [INF] Request finished HTTP/1.1 GET http://localhost:5265/Admin/Dashboard - 302 0 null 4.7369ms +2026-07-11 18:52:14.308 +09:00 [INF] Request starting HTTP/1.1 GET http://localhost:5265/Admin/Dashboard - null null +2026-07-11 18:52:14.309 +09:00 [INF] Authorization failed. These requirements were not met: +DenyAnonymousAuthorizationRequirement: Requires an authenticated user. +2026-07-11 18:52:14.310 +09:00 [INF] AuthenticationScheme: AdminCookie was challenged. +2026-07-11 18:52:14.310 +09:00 [INF] HTTP GET /Admin/Dashboard responded 302 in 1.4155 ms +2026-07-11 18:52:14.310 +09:00 [INF] Request finished HTTP/1.1 GET http://localhost:5265/Admin/Dashboard - 302 0 null 1.7032ms +2026-07-11 18:52:14.410 +09:00 [INF] Request starting HTTP/1.1 GET http://localhost:5265/Admin/Users - null null +2026-07-11 18:52:14.411 +09:00 [INF] Authorization failed. These requirements were not met: +DenyAnonymousAuthorizationRequirement: Requires an authenticated user. +2026-07-11 18:52:14.411 +09:00 [INF] AuthenticationScheme: AdminCookie was challenged. +2026-07-11 18:52:14.411 +09:00 [INF] HTTP GET /Admin/Users responded 302 in 0.4739 ms +2026-07-11 18:52:14.411 +09:00 [INF] Request finished HTTP/1.1 GET http://localhost:5265/Admin/Users - 302 0 null 0.7314ms +2026-07-11 18:52:14.523 +09:00 [INF] Request starting HTTP/1.1 GET http://localhost:5265/Admin/Collection - null null +2026-07-11 18:52:14.523 +09:00 [INF] Authorization failed. These requirements were not met: +DenyAnonymousAuthorizationRequirement: Requires an authenticated user. +2026-07-11 18:52:14.523 +09:00 [INF] AuthenticationScheme: AdminCookie was challenged. +2026-07-11 18:52:14.524 +09:00 [INF] HTTP GET /Admin/Collection responded 302 in 0.6342 ms +2026-07-11 18:52:14.524 +09:00 [INF] Request finished HTTP/1.1 GET http://localhost:5265/Admin/Collection - 302 0 null 1.6071ms +2026-07-11 18:52:14.642 +09:00 [INF] Request starting HTTP/1.1 GET http://localhost:5265/Admin/Monitoring - null null +2026-07-11 18:52:14.643 +09:00 [INF] Authorization failed. These requirements were not met: +DenyAnonymousAuthorizationRequirement: Requires an authenticated user. +2026-07-11 18:52:14.643 +09:00 [INF] AuthenticationScheme: AdminCookie was challenged. +2026-07-11 18:52:14.643 +09:00 [INF] HTTP GET /Admin/Monitoring responded 302 in 0.4305 ms +2026-07-11 18:52:14.643 +09:00 [INF] Request finished HTTP/1.1 GET http://localhost:5265/Admin/Monitoring - 302 0 null 0.677ms +2026-07-11 18:52:14.741 +09:00 [INF] Request starting HTTP/1.1 GET http://localhost:5265/Admin/Operations - null null +2026-07-11 18:52:14.741 +09:00 [INF] Authorization failed. These requirements were not met: +DenyAnonymousAuthorizationRequirement: Requires an authenticated user. +2026-07-11 18:52:14.742 +09:00 [INF] AuthenticationScheme: AdminCookie was challenged. +2026-07-11 18:52:14.742 +09:00 [INF] HTTP GET /Admin/Operations responded 302 in 0.4306 ms +2026-07-11 18:52:14.742 +09:00 [INF] Request finished HTTP/1.1 GET http://localhost:5265/Admin/Operations - 302 0 null 0.6763ms +2026-07-11 21:04:06.174 +09:00 [INF] Request starting HTTP/1.1 GET http://localhost:5265/login - null null +2026-07-11 21:04:06.177 +09:00 [INF] Executing endpoint 'HTTP: GET /login' +2026-07-11 21:04:06.177 +09:00 [INF] Executed endpoint 'HTTP: GET /login' +2026-07-11 21:04:06.178 +09:00 [INF] HTTP GET /login responded 302 in 2.1715 ms +2026-07-11 21:04:06.178 +09:00 [INF] Request finished HTTP/1.1 GET http://localhost:5265/login - 302 0 null 3.7332ms +2026-07-11 21:04:06.191 +09:00 [INF] Request starting HTTP/1.1 GET http://localhost:5265/Account/Login - null null +2026-07-11 21:04:06.194 +09:00 [INF] Executing endpoint '/Account/Login' +2026-07-11 21:04:06.195 +09:00 [INF] Route matched with {page = "/Account/Login"}. Executing page /Account/Login +2026-07-11 21:04:06.198 +09:00 [INF] Executing handler method QuantEngine.Web.Pages.Account.LoginModel.OnGet - ModelState is "Valid" +2026-07-11 21:04:06.199 +09:00 [INF] Executed handler method OnGet, returned result . +2026-07-11 21:04:06.199 +09:00 [INF] Executing an implicit handler method - ModelState is "Valid" +2026-07-11 21:04:06.200 +09:00 [INF] Executed an implicit handler method, returned result Microsoft.AspNetCore.Mvc.RazorPages.PageResult. +2026-07-11 21:04:06.212 +09:00 [INF] Executed page /Account/Login in 16.6942ms +2026-07-11 21:04:06.212 +09:00 [INF] Executed endpoint '/Account/Login' +2026-07-11 21:04:06.212 +09:00 [INF] HTTP GET /Account/Login responded 200 in 21.4926 ms +2026-07-11 21:04:06.213 +09:00 [INF] Request finished HTTP/1.1 GET http://localhost:5265/Account/Login - 200 null text/html; charset=utf-8 22.1426ms +2026-07-11 21:04:06.222 +09:00 [INF] Request starting HTTP/1.1 GET http://localhost:5265/login - null null +2026-07-11 21:04:06.223 +09:00 [INF] Executing endpoint 'HTTP: GET /login' +2026-07-11 21:04:06.223 +09:00 [INF] Executed endpoint 'HTTP: GET /login' +2026-07-11 21:04:06.223 +09:00 [INF] HTTP GET /login responded 302 in 0.5991 ms +2026-07-11 21:04:06.223 +09:00 [INF] Request finished HTTP/1.1 GET http://localhost:5265/login - 302 0 null 1.2095ms +2026-07-11 21:04:06.226 +09:00 [INF] Request starting HTTP/1.1 GET http://localhost:5265/Account/Login - null null +2026-07-11 21:04:06.227 +09:00 [INF] Executing endpoint '/Account/Login' +2026-07-11 21:04:06.227 +09:00 [INF] Route matched with {page = "/Account/Login"}. Executing page /Account/Login +2026-07-11 21:04:06.231 +09:00 [INF] Executing handler method QuantEngine.Web.Pages.Account.LoginModel.OnGet - ModelState is "Valid" +2026-07-11 21:04:06.232 +09:00 [INF] Executed handler method OnGet, returned result . +2026-07-11 21:04:06.232 +09:00 [INF] Executing an implicit handler method - ModelState is "Valid" +2026-07-11 21:04:06.233 +09:00 [INF] Executed an implicit handler method, returned result Microsoft.AspNetCore.Mvc.RazorPages.PageResult. +2026-07-11 21:04:06.238 +09:00 [INF] Executed page /Account/Login in 10.6054ms +2026-07-11 21:04:06.238 +09:00 [INF] Executed endpoint '/Account/Login' +2026-07-11 21:04:06.238 +09:00 [INF] HTTP GET /Account/Login responded 200 in 11.7060 ms +2026-07-11 21:04:06.238 +09:00 [INF] Request finished HTTP/1.1 GET http://localhost:5265/Account/Login - 200 null text/html; charset=utf-8 12.2449ms +2026-07-11 21:05:05.597 +09:00 [INF] Request starting HTTP/1.1 GET http://localhost:5265/login - null null +2026-07-11 21:05:05.597 +09:00 [INF] Executing endpoint 'HTTP: GET /login' +2026-07-11 21:05:05.598 +09:00 [INF] Executed endpoint 'HTTP: GET /login' +2026-07-11 21:05:05.598 +09:00 [INF] HTTP GET /login responded 302 in 0.4316 ms +2026-07-11 21:05:05.598 +09:00 [INF] Request finished HTTP/1.1 GET http://localhost:5265/login - 302 0 null 0.8539ms +2026-07-11 21:05:05.606 +09:00 [INF] Request starting HTTP/1.1 GET http://localhost:5265/Account/Login - null null +2026-07-11 21:05:05.606 +09:00 [INF] Executing endpoint '/Account/Login' +2026-07-11 21:05:05.606 +09:00 [INF] Route matched with {page = "/Account/Login"}. Executing page /Account/Login +2026-07-11 21:05:05.606 +09:00 [INF] Executing handler method QuantEngine.Web.Pages.Account.LoginModel.OnGet - ModelState is "Valid" +2026-07-11 21:05:05.606 +09:00 [INF] Executed handler method OnGet, returned result . +2026-07-11 21:05:05.607 +09:00 [INF] Executing an implicit handler method - ModelState is "Valid" +2026-07-11 21:05:05.607 +09:00 [INF] Executed an implicit handler method, returned result Microsoft.AspNetCore.Mvc.RazorPages.PageResult. +2026-07-11 21:05:05.607 +09:00 [INF] Executed page /Account/Login in 1.1238ms +2026-07-11 21:05:05.607 +09:00 [INF] Executed endpoint '/Account/Login' +2026-07-11 21:05:05.607 +09:00 [INF] HTTP GET /Account/Login responded 200 in 1.6523 ms +2026-07-11 21:05:05.607 +09:00 [INF] Request finished HTTP/1.1 GET http://localhost:5265/Account/Login - 200 null text/html; charset=utf-8 1.9459ms +2026-07-11 21:05:05.611 +09:00 [INF] Request starting HTTP/1.1 GET http://localhost:5265/login - null null +2026-07-11 21:05:05.612 +09:00 [INF] Executing endpoint 'HTTP: GET /login' +2026-07-11 21:05:05.612 +09:00 [INF] Executed endpoint 'HTTP: GET /login' +2026-07-11 21:05:05.612 +09:00 [INF] HTTP GET /login responded 302 in 0.3471 ms +2026-07-11 21:05:05.612 +09:00 [INF] Request finished HTTP/1.1 GET http://localhost:5265/login - 302 0 null 0.603ms +2026-07-11 21:05:05.616 +09:00 [INF] Request starting HTTP/1.1 GET http://localhost:5265/Account/Login - null null +2026-07-11 21:05:05.616 +09:00 [INF] Executing endpoint '/Account/Login' +2026-07-11 21:05:05.616 +09:00 [INF] Route matched with {page = "/Account/Login"}. Executing page /Account/Login +2026-07-11 21:05:05.616 +09:00 [INF] Executing handler method QuantEngine.Web.Pages.Account.LoginModel.OnGet - ModelState is "Valid" +2026-07-11 21:05:05.616 +09:00 [INF] Executed handler method OnGet, returned result . +2026-07-11 21:05:05.616 +09:00 [INF] Executing an implicit handler method - ModelState is "Valid" +2026-07-11 21:05:05.616 +09:00 [INF] Executed an implicit handler method, returned result Microsoft.AspNetCore.Mvc.RazorPages.PageResult. +2026-07-11 21:05:05.617 +09:00 [INF] Executed page /Account/Login in 0.5619ms +2026-07-11 21:05:05.617 +09:00 [INF] Executed endpoint '/Account/Login' +2026-07-11 21:05:05.617 +09:00 [INF] HTTP GET /Account/Login responded 200 in 0.9286 ms +2026-07-11 21:05:05.617 +09:00 [INF] Request finished HTTP/1.1 GET http://localhost:5265/Account/Login - 200 null text/html; charset=utf-8 1.1671ms diff --git a/src/dotnet/QuantEngine.Web/logs/quantengine-20260712.log b/src/dotnet/QuantEngine.Web/logs/quantengine-20260712.log new file mode 100644 index 00000000..6153f13f --- /dev/null +++ b/src/dotnet/QuantEngine.Web/logs/quantengine-20260712.log @@ -0,0 +1,610 @@ +2026-07-12 01:49:59.393 +09:00 [INF] Registered 10 endpoints in 432 milliseconds. +2026-07-12 01:49:59.756 +09:00 [INF] 🔄 Starting database migration with DbUp... +2026-07-12 01:50:02.068 +09:00 [INF] ✅ Database migration completed successfully +2026-07-12 01:50:05.349 +09:00 [INF] Database migration and initialization successful +2026-07-12 01:50:05.388 +09:00 [INF] User profile is available. Using 'C:\Users\kjh20\AppData\Local\ASP.NET\DataProtection-Keys' as key repository and Windows DPAPI to encrypt keys at rest. +2026-07-12 01:50:06.028 +09:00 [INF] Start installing Hangfire SQL objects... +2026-07-12 01:50:10.702 +09:00 [INF] Hangfire SQL objects installed. +2026-07-12 01:50:10.728 +09:00 [WRN] Hangfire setup failed: Cannot resolve scoped service 'QuantEngine.Web.Services.SchedulerService' from root provider. +2026-07-12 01:50:10.877 +09:00 [ERR] Hosting failed to start +System.IO.IOException: Failed to bind to address http://127.0.0.1:5265: address already in use. + ---> Microsoft.AspNetCore.Connections.AddressInUseException: 각 소켓 주소(프로토콜/네트워크 주소/포트)는 하나만 사용할 수 있습니다. + ---> System.Net.Sockets.SocketException (10048): 각 소켓 주소(프로토콜/네트워크 주소/포트)는 하나만 사용할 수 있습니다. + at System.Net.Sockets.Socket.UpdateStatusAfterSocketErrorAndThrowException(SocketError error, Boolean disconnectOnFailure, String callerName) + at System.Net.Sockets.Socket.DoBind(EndPoint endPointSnapshot, SocketAddress socketAddress) + at System.Net.Sockets.Socket.Bind(EndPoint localEP) + at Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketTransportOptions.CreateDefaultBoundListenSocket(EndPoint endpoint) + at Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketConnectionListener.Bind() + --- End of inner exception stack trace --- + at Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketConnectionListener.Bind() + at Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketTransportFactory.BindAsync(EndPoint endpoint, CancellationToken cancellationToken) + at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.TransportManager.BindAsync(EndPoint endPoint, ConnectionDelegate connectionDelegate, EndpointConfig endpointConfig, CancellationToken cancellationToken) + at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.<>c__DisplayClass28_0`1.<g__OnBind|0>d.MoveNext() +--- End of stack trace from previous location --- + at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.BindEndpointAsync(ListenOptions endpoint, AddressBindContext context, CancellationToken cancellationToken) + --- End of inner exception stack trace --- + at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.BindEndpointAsync(ListenOptions endpoint, AddressBindContext context, CancellationToken cancellationToken) + at Microsoft.AspNetCore.Server.Kestrel.Core.LocalhostListenOptions.BindAsync(AddressBindContext context, CancellationToken cancellationToken) + at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.AddressesStrategy.BindAsync(AddressBindContext context, CancellationToken cancellationToken) + at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.BindAsync(CancellationToken cancellationToken) + at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.StartAsync[TContext](IHttpApplication`1 application, CancellationToken cancellationToken) + at Microsoft.AspNetCore.Hosting.GenericWebHostService.StartAsync(CancellationToken cancellationToken) + at Microsoft.Extensions.Hosting.Internal.Host.b__14_1(IHostedService service, CancellationToken token) + at Microsoft.Extensions.Hosting.Internal.Host.ForeachService[T](IEnumerable`1 services, CancellationToken token, Boolean concurrent, Boolean abortOnFirstException, List`1 exceptions, Func`3 operation) +2026-07-12 01:50:10.905 +09:00 [FTL] Application terminated unexpectedly +System.IO.IOException: Failed to bind to address http://127.0.0.1:5265: address already in use. + ---> Microsoft.AspNetCore.Connections.AddressInUseException: 각 소켓 주소(프로토콜/네트워크 주소/포트)는 하나만 사용할 수 있습니다. + ---> System.Net.Sockets.SocketException (10048): 각 소켓 주소(프로토콜/네트워크 주소/포트)는 하나만 사용할 수 있습니다. + at System.Net.Sockets.Socket.UpdateStatusAfterSocketErrorAndThrowException(SocketError error, Boolean disconnectOnFailure, String callerName) + at System.Net.Sockets.Socket.DoBind(EndPoint endPointSnapshot, SocketAddress socketAddress) + at System.Net.Sockets.Socket.Bind(EndPoint localEP) + at Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketTransportOptions.CreateDefaultBoundListenSocket(EndPoint endpoint) + at Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketConnectionListener.Bind() + --- End of inner exception stack trace --- + at Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketConnectionListener.Bind() + at Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets.SocketTransportFactory.BindAsync(EndPoint endpoint, CancellationToken cancellationToken) + at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.TransportManager.BindAsync(EndPoint endPoint, ConnectionDelegate connectionDelegate, EndpointConfig endpointConfig, CancellationToken cancellationToken) + at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.<>c__DisplayClass28_0`1.<g__OnBind|0>d.MoveNext() +--- End of stack trace from previous location --- + at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.BindEndpointAsync(ListenOptions endpoint, AddressBindContext context, CancellationToken cancellationToken) + --- End of inner exception stack trace --- + at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.BindEndpointAsync(ListenOptions endpoint, AddressBindContext context, CancellationToken cancellationToken) + at Microsoft.AspNetCore.Server.Kestrel.Core.LocalhostListenOptions.BindAsync(AddressBindContext context, CancellationToken cancellationToken) + at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.AddressesStrategy.BindAsync(AddressBindContext context, CancellationToken cancellationToken) + at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.BindAsync(CancellationToken cancellationToken) + at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.StartAsync[TContext](IHttpApplication`1 application, CancellationToken cancellationToken) + at Microsoft.AspNetCore.Hosting.GenericWebHostService.StartAsync(CancellationToken cancellationToken) + at Microsoft.Extensions.Hosting.Internal.Host.b__14_1(IHostedService service, CancellationToken token) + at Microsoft.Extensions.Hosting.Internal.Host.ForeachService[T](IEnumerable`1 services, CancellationToken token, Boolean concurrent, Boolean abortOnFirstException, List`1 exceptions, Func`3 operation) + at Microsoft.Extensions.Hosting.Internal.Host.StartAsync(CancellationToken cancellationToken) + at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token) + at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token) + at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.Run(IHost host) + at Program.
$(String[] args) in C:\Temp\data_feed\src\dotnet\QuantEngine.Web\Program.cs:line 178 +2026-07-12 01:50:55.147 +09:00 [INF] Registered 10 endpoints in 161 milliseconds. +2026-07-12 01:50:55.188 +09:00 [INF] 🔄 Starting database migration with DbUp... +2026-07-12 01:50:57.488 +09:00 [INF] ✅ Database migration completed successfully +2026-07-12 01:51:00.589 +09:00 [INF] Database migration and initialization successful +2026-07-12 01:51:00.610 +09:00 [INF] User profile is available. Using 'C:\Users\kjh20\AppData\Local\ASP.NET\DataProtection-Keys' as key repository and Windows DPAPI to encrypt keys at rest. +2026-07-12 01:51:00.632 +09:00 [INF] Start installing Hangfire SQL objects... +2026-07-12 01:51:05.254 +09:00 [INF] Hangfire SQL objects installed. +2026-07-12 01:51:05.261 +09:00 [WRN] Hangfire setup failed: Cannot resolve scoped service 'QuantEngine.Web.Services.SchedulerService' from root provider. +2026-07-12 01:51:05.409 +09:00 [INF] Now listening on: http://localhost:5265 +2026-07-12 01:51:05.417 +09:00 [INF] Starting Hangfire Server using job storage: 'PostgreSQL Server: Host: 127.0.0.1, DB: quantenginedb, Schema: hangfire' +2026-07-12 01:51:05.417 +09:00 [INF] Using the following options for PostgreSQL job storage: +2026-07-12 01:51:05.418 +09:00 [INF] Queue poll interval: 00:00:15. +2026-07-12 01:51:05.418 +09:00 [INF] Invisibility timeout: 00:30:00. +2026-07-12 01:51:05.418 +09:00 [INF] Use sliding invisibility timeout: False. +2026-07-12 01:51:05.418 +09:00 [INF] Using the following options for Hangfire Server: + Worker count: 32 + Listening queues: 'default' + Shutdown timeout: 00:00:15 + Schedule polling interval: 00:00:15 +2026-07-12 01:51:05.434 +09:00 [INF] Application started. Press Ctrl+C to shut down. +2026-07-12 01:51:05.434 +09:00 [INF] Hosting environment: Development +2026-07-12 01:51:05.434 +09:00 [INF] Content root path: C:\Temp\data_feed\src\dotnet\QuantEngine.Web +2026-07-12 01:51:05.741 +09:00 [INF] Server kimjaehyun-note:5816:6e6d8857 successfully announced in 304.3044 ms +2026-07-12 01:51:05.744 +09:00 [INF] Server kimjaehyun-note:5816:6e6d8857 is starting the registered dispatchers: ServerWatchdog, ServerJobCancellationWatcher, ExpirationManager, CountersAggregator, Worker, DelayedJobScheduler, RecurringJobScheduler... +2026-07-12 01:51:05.762 +09:00 [INF] Server kimjaehyun-note:5816:6e6d8857 all the dispatchers started +2026-07-12 01:51:16.896 +09:00 [INF] Request starting HTTP/1.1 GET http://localhost:5265/Account/Login - null null +2026-07-12 01:51:16.900 +09:00 [WRN] Failed to determine the https port for redirect. +2026-07-12 01:51:16.932 +09:00 [INF] Executing endpoint '/Account/Login' +2026-07-12 01:51:16.958 +09:00 [INF] Route matched with {page = "/Account/Login"}. Executing page /Account/Login +2026-07-12 01:51:16.970 +09:00 [INF] Executing handler method QuantEngine.Web.Pages.Account.LoginModel.OnGet - ModelState is "Valid" +2026-07-12 01:51:16.972 +09:00 [INF] Executed handler method OnGet, returned result . +2026-07-12 01:51:16.977 +09:00 [INF] Executing an implicit handler method - ModelState is "Valid" +2026-07-12 01:51:16.978 +09:00 [INF] Executed an implicit handler method, returned result Microsoft.AspNetCore.Mvc.RazorPages.PageResult. +2026-07-12 01:51:17.024 +09:00 [INF] Executed page /Account/Login in 63.0425ms +2026-07-12 01:51:17.025 +09:00 [INF] Executed endpoint '/Account/Login' +2026-07-12 01:51:17.026 +09:00 [INF] HTTP GET /Account/Login responded 200 in 126.7878 ms +2026-07-12 01:51:17.028 +09:00 [INF] Request finished HTTP/1.1 GET http://localhost:5265/Account/Login - 200 null text/html; charset=utf-8 132.7688ms +2026-07-12 01:51:42.662 +09:00 [INF] Request starting HTTP/1.1 GET http://localhost:5265/Account/Login - null null +2026-07-12 01:51:42.685 +09:00 [INF] Executing endpoint '/Account/Login' +2026-07-12 01:51:42.685 +09:00 [INF] Route matched with {page = "/Account/Login"}. Executing page /Account/Login +2026-07-12 01:51:42.687 +09:00 [INF] Executing handler method QuantEngine.Web.Pages.Account.LoginModel.OnGet - ModelState is "Valid" +2026-07-12 01:51:42.687 +09:00 [INF] Executed handler method OnGet, returned result . +2026-07-12 01:51:42.687 +09:00 [INF] Executing an implicit handler method - ModelState is "Valid" +2026-07-12 01:51:42.687 +09:00 [INF] Executed an implicit handler method, returned result Microsoft.AspNetCore.Mvc.RazorPages.PageResult. +2026-07-12 01:51:42.694 +09:00 [INF] Executed page /Account/Login in 9.2179ms +2026-07-12 01:51:42.695 +09:00 [INF] Executed endpoint '/Account/Login' +2026-07-12 01:51:42.695 +09:00 [INF] HTTP GET /Account/Login responded 200 in 32.3239 ms +2026-07-12 01:51:42.695 +09:00 [INF] Request finished HTTP/1.1 GET http://localhost:5265/Account/Login - 200 null text/html; charset=utf-8 34.0934ms +2026-07-12 01:51:43.344 +09:00 [INF] Request starting HTTP/1.1 POST http://localhost:5265/Account/Login - application/x-www-form-urlencoded 218 +2026-07-12 01:51:43.352 +09:00 [INF] Executing endpoint '/Account/Login' +2026-07-12 01:51:43.353 +09:00 [INF] Route matched with {page = "/Account/Login"}. Executing page /Account/Login +2026-07-12 01:51:43.402 +09:00 [INF] Executing handler method QuantEngine.Web.Pages.Account.LoginModel.OnPostAsync - ModelState is "Valid" +2026-07-12 01:51:44.200 +09:00 [INF] AuthenticationScheme: AdminCookie signed in. +2026-07-12 01:51:44.201 +09:00 [INF] [Login] User 'admin' authenticated successfully from ::1 +2026-07-12 01:51:44.205 +09:00 [INF] Executed handler method OnPostAsync, returned result Microsoft.AspNetCore.Mvc.LocalRedirectResult. +2026-07-12 01:51:44.210 +09:00 [INF] Executing LocalRedirectResult, redirecting to /Admin/Dashboard. +2026-07-12 01:51:44.213 +09:00 [INF] Executed page /Account/Login in 859.92ms +2026-07-12 01:51:44.214 +09:00 [INF] Executed endpoint '/Account/Login' +2026-07-12 01:51:44.216 +09:00 [INF] HTTP POST /Account/Login responded 302 in 871.2169 ms +2026-07-12 01:51:44.218 +09:00 [INF] Request finished HTTP/1.1 POST http://localhost:5265/Account/Login - 302 0 null 873.6838ms +2026-07-12 01:51:44.222 +09:00 [INF] Request starting HTTP/1.1 GET http://localhost:5265/Admin/Dashboard - null null +2026-07-12 01:51:44.232 +09:00 [INF] Executing endpoint '/Admin/Dashboard/Index' +2026-07-12 01:51:44.252 +09:00 [INF] Route matched with {page = "/Admin/Dashboard/Index"}. Executing page /Admin/Dashboard/Index +2026-07-12 01:51:44.253 +09:00 [INF] Executing handler method QuantEngine.Web.Pages.Admin.Dashboard.IndexModel.OnGetAsync - ModelState is "Valid" +2026-07-12 01:51:45.151 +09:00 [INF] Executed handler method OnGetAsync, returned result . +2026-07-12 01:51:45.151 +09:00 [INF] Executing an implicit handler method - ModelState is "Valid" +2026-07-12 01:51:45.152 +09:00 [INF] Executed an implicit handler method, returned result Microsoft.AspNetCore.Mvc.RazorPages.PageResult. +2026-07-12 01:51:45.229 +09:00 [INF] Executed page /Admin/Dashboard/Index in 976.4261ms +2026-07-12 01:51:45.229 +09:00 [INF] Executed endpoint '/Admin/Dashboard/Index' +2026-07-12 01:51:45.230 +09:00 [INF] HTTP GET /Admin/Dashboard responded 200 in 1007.5596 ms +2026-07-12 01:51:45.230 +09:00 [INF] Request finished HTTP/1.1 GET http://localhost:5265/Admin/Dashboard - 200 null text/html; charset=utf-8 1008.1707ms +2026-07-12 01:51:45.238 +09:00 [INF] Request starting HTTP/1.1 GET http://localhost:5265/css/admin.css - null null +2026-07-12 01:51:45.255 +09:00 [INF] Sending file. Request path: '/css/admin.css'. Physical path: 'C:\Temp\data_feed\src\dotnet\QuantEngine.Web\wwwroot\css\admin.css' +2026-07-12 01:51:45.256 +09:00 [INF] HTTP GET /css/admin.css responded 200 in 17.9079 ms +2026-07-12 01:51:45.256 +09:00 [INF] Request finished HTTP/1.1 GET http://localhost:5265/css/admin.css - 200 3956 text/css 18.4204ms +2026-07-12 01:51:45.933 +09:00 [INF] Request starting HTTP/1.1 GET http://localhost:5265/Admin/Operations - null null +2026-07-12 01:51:45.937 +09:00 [INF] Executing endpoint '/Admin/Operations/Index' +2026-07-12 01:51:45.951 +09:00 [INF] Route matched with {page = "/Admin/Operations/Index"}. Executing page /Admin/Operations/Index +2026-07-12 01:51:45.952 +09:00 [INF] Executing handler method QuantEngine.Web.Pages.Admin.Operations.IndexModel.OnGetAsync - ModelState is "Valid" +2026-07-12 01:51:48.823 +09:00 [INF] Operations data loaded from Hangfire (4 recurring jobs, 2 servers) +2026-07-12 01:51:48.824 +09:00 [INF] Executed handler method OnGetAsync, returned result . +2026-07-12 01:51:48.824 +09:00 [INF] Executing an implicit handler method - ModelState is "Valid" +2026-07-12 01:51:48.824 +09:00 [INF] Executed an implicit handler method, returned result Microsoft.AspNetCore.Mvc.RazorPages.PageResult. +2026-07-12 01:51:48.846 +09:00 [INF] Executed page /Admin/Operations/Index in 2894.9938ms +2026-07-12 01:51:48.847 +09:00 [INF] Executed endpoint '/Admin/Operations/Index' +2026-07-12 01:51:48.847 +09:00 [INF] HTTP GET /Admin/Operations responded 200 in 2913.4775 ms +2026-07-12 01:51:48.847 +09:00 [INF] Request finished HTTP/1.1 GET http://localhost:5265/Admin/Operations - 200 null text/html; charset=utf-8 2914.1336ms +2026-07-12 01:51:49.732 +09:00 [INF] Request starting HTTP/1.1 GET http://localhost:5265/Account/Login - null null +2026-07-12 01:51:49.732 +09:00 [INF] Executing endpoint '/Account/Login' +2026-07-12 01:51:49.732 +09:00 [INF] Route matched with {page = "/Account/Login"}. Executing page /Account/Login +2026-07-12 01:51:49.732 +09:00 [INF] Executing handler method QuantEngine.Web.Pages.Account.LoginModel.OnGet - ModelState is "Valid" +2026-07-12 01:51:49.733 +09:00 [INF] Executed handler method OnGet, returned result . +2026-07-12 01:51:49.733 +09:00 [INF] Executing an implicit handler method - ModelState is "Valid" +2026-07-12 01:51:49.733 +09:00 [INF] Executed an implicit handler method, returned result Microsoft.AspNetCore.Mvc.RazorPages.PageResult. +2026-07-12 01:51:49.735 +09:00 [INF] Executed page /Account/Login in 2.6158ms +2026-07-12 01:51:49.735 +09:00 [INF] Executed endpoint '/Account/Login' +2026-07-12 01:51:49.735 +09:00 [INF] HTTP GET /Account/Login responded 200 in 3.4623 ms +2026-07-12 01:51:49.735 +09:00 [INF] Request finished HTTP/1.1 GET http://localhost:5265/Account/Login - 200 null text/html; charset=utf-8 3.8328ms +2026-07-12 01:51:50.371 +09:00 [INF] Request starting HTTP/1.1 POST http://localhost:5265/Account/Login - application/x-www-form-urlencoded 218 +2026-07-12 01:51:50.371 +09:00 [INF] Executing endpoint '/Account/Login' +2026-07-12 01:51:50.371 +09:00 [INF] Route matched with {page = "/Account/Login"}. Executing page /Account/Login +2026-07-12 01:51:50.373 +09:00 [INF] Executing handler method QuantEngine.Web.Pages.Account.LoginModel.OnPostAsync - ModelState is "Valid" +2026-07-12 01:51:50.857 +09:00 [INF] AuthenticationScheme: AdminCookie signed in. +2026-07-12 01:51:50.857 +09:00 [INF] [Login] User 'admin' authenticated successfully from ::1 +2026-07-12 01:51:50.857 +09:00 [INF] Executed handler method OnPostAsync, returned result Microsoft.AspNetCore.Mvc.LocalRedirectResult. +2026-07-12 01:51:50.858 +09:00 [INF] Executing LocalRedirectResult, redirecting to /Admin/Dashboard. +2026-07-12 01:51:50.858 +09:00 [INF] Executed page /Account/Login in 486.6264ms +2026-07-12 01:51:50.858 +09:00 [INF] Executed endpoint '/Account/Login' +2026-07-12 01:51:50.858 +09:00 [INF] HTTP POST /Account/Login responded 302 in 487.2066 ms +2026-07-12 01:51:50.858 +09:00 [INF] Request finished HTTP/1.1 POST http://localhost:5265/Account/Login - 302 0 null 487.636ms +2026-07-12 01:51:50.862 +09:00 [INF] Request starting HTTP/1.1 GET http://localhost:5265/Admin/Dashboard - null null +2026-07-12 01:51:50.865 +09:00 [INF] Executing endpoint '/Admin/Dashboard/Index' +2026-07-12 01:51:50.865 +09:00 [INF] Route matched with {page = "/Admin/Dashboard/Index"}. Executing page /Admin/Dashboard/Index +2026-07-12 01:51:50.865 +09:00 [INF] Executing handler method QuantEngine.Web.Pages.Admin.Dashboard.IndexModel.OnGetAsync - ModelState is "Valid" +2026-07-12 01:51:51.755 +09:00 [INF] Executed handler method OnGetAsync, returned result . +2026-07-12 01:51:51.755 +09:00 [INF] Executing an implicit handler method - ModelState is "Valid" +2026-07-12 01:51:51.755 +09:00 [INF] Executed an implicit handler method, returned result Microsoft.AspNetCore.Mvc.RazorPages.PageResult. +2026-07-12 01:51:51.757 +09:00 [INF] Executed page /Admin/Dashboard/Index in 891.4622ms +2026-07-12 01:51:51.757 +09:00 [INF] Executed endpoint '/Admin/Dashboard/Index' +2026-07-12 01:51:51.757 +09:00 [INF] HTTP GET /Admin/Dashboard responded 200 in 894.9356 ms +2026-07-12 01:51:51.757 +09:00 [INF] Request finished HTTP/1.1 GET http://localhost:5265/Admin/Dashboard - 200 null text/html; charset=utf-8 895.2421ms +2026-07-12 01:51:51.766 +09:00 [INF] Request starting HTTP/1.1 GET http://localhost:5265/css/admin.css - null null +2026-07-12 01:51:51.779 +09:00 [INF] Sending file. Request path: '/css/admin.css'. Physical path: 'C:\Temp\data_feed\src\dotnet\QuantEngine.Web\wwwroot\css\admin.css' +2026-07-12 01:51:51.779 +09:00 [INF] HTTP GET /css/admin.css responded 200 in 12.9029 ms +2026-07-12 01:51:51.780 +09:00 [INF] Request finished HTTP/1.1 GET http://localhost:5265/css/admin.css - 200 3956 text/css 13.3629ms +2026-07-12 01:52:45.383 +09:00 [INF] Request starting HTTP/1.1 GET http://localhost:5265/Account/Login - null null +2026-07-12 01:52:45.383 +09:00 [INF] Executing endpoint '/Account/Login' +2026-07-12 01:52:45.383 +09:00 [INF] Route matched with {page = "/Account/Login"}. Executing page /Account/Login +2026-07-12 01:52:45.383 +09:00 [INF] Executing handler method QuantEngine.Web.Pages.Account.LoginModel.OnGet - ModelState is "Valid" +2026-07-12 01:52:45.383 +09:00 [INF] Executed handler method OnGet, returned result . +2026-07-12 01:52:45.383 +09:00 [INF] Executing an implicit handler method - ModelState is "Valid" +2026-07-12 01:52:45.383 +09:00 [INF] Executed an implicit handler method, returned result Microsoft.AspNetCore.Mvc.RazorPages.PageResult. +2026-07-12 01:52:45.383 +09:00 [INF] Executed page /Account/Login in 0.5097ms +2026-07-12 01:52:45.384 +09:00 [INF] Executed endpoint '/Account/Login' +2026-07-12 01:52:45.384 +09:00 [INF] HTTP GET /Account/Login responded 200 in 0.8798 ms +2026-07-12 01:52:45.384 +09:00 [INF] Request finished HTTP/1.1 GET http://localhost:5265/Account/Login - 200 null text/html; charset=utf-8 1.0536ms +2026-07-12 01:52:46.018 +09:00 [INF] Request starting HTTP/1.1 POST http://localhost:5265/Account/Login - application/x-www-form-urlencoded 218 +2026-07-12 01:52:46.018 +09:00 [INF] Executing endpoint '/Account/Login' +2026-07-12 01:52:46.018 +09:00 [INF] Route matched with {page = "/Account/Login"}. Executing page /Account/Login +2026-07-12 01:52:46.019 +09:00 [INF] Executing handler method QuantEngine.Web.Pages.Account.LoginModel.OnPostAsync - ModelState is "Valid" +2026-07-12 01:52:46.475 +09:00 [INF] AuthenticationScheme: AdminCookie signed in. +2026-07-12 01:52:46.475 +09:00 [INF] [Login] User 'admin' authenticated successfully from ::1 +2026-07-12 01:52:46.475 +09:00 [INF] Executed handler method OnPostAsync, returned result Microsoft.AspNetCore.Mvc.LocalRedirectResult. +2026-07-12 01:52:46.475 +09:00 [INF] Executing LocalRedirectResult, redirecting to /Admin/Dashboard. +2026-07-12 01:52:46.475 +09:00 [INF] Executed page /Account/Login in 456.9281ms +2026-07-12 01:52:46.475 +09:00 [INF] Executed endpoint '/Account/Login' +2026-07-12 01:52:46.475 +09:00 [INF] HTTP POST /Account/Login responded 302 in 457.3632 ms +2026-07-12 01:52:46.475 +09:00 [INF] Request finished HTTP/1.1 POST http://localhost:5265/Account/Login - 302 0 null 457.5915ms +2026-07-12 01:52:46.478 +09:00 [INF] Request starting HTTP/1.1 GET http://localhost:5265/Admin/Dashboard - null null +2026-07-12 01:52:46.479 +09:00 [INF] Executing endpoint '/Admin/Dashboard/Index' +2026-07-12 01:52:46.479 +09:00 [INF] Route matched with {page = "/Admin/Dashboard/Index"}. Executing page /Admin/Dashboard/Index +2026-07-12 01:52:46.479 +09:00 [INF] Executing handler method QuantEngine.Web.Pages.Admin.Dashboard.IndexModel.OnGetAsync - ModelState is "Valid" +2026-07-12 01:52:47.363 +09:00 [INF] Executed handler method OnGetAsync, returned result . +2026-07-12 01:52:47.363 +09:00 [INF] Executing an implicit handler method - ModelState is "Valid" +2026-07-12 01:52:47.363 +09:00 [INF] Executed an implicit handler method, returned result Microsoft.AspNetCore.Mvc.RazorPages.PageResult. +2026-07-12 01:52:47.366 +09:00 [INF] Executed page /Admin/Dashboard/Index in 886.9837ms +2026-07-12 01:52:47.366 +09:00 [INF] Executed endpoint '/Admin/Dashboard/Index' +2026-07-12 01:52:47.366 +09:00 [INF] HTTP GET /Admin/Dashboard responded 200 in 888.0406 ms +2026-07-12 01:52:47.367 +09:00 [INF] Request finished HTTP/1.1 GET http://localhost:5265/Admin/Dashboard - 200 null text/html; charset=utf-8 888.4149ms +2026-07-12 01:52:47.378 +09:00 [INF] Request starting HTTP/1.1 GET http://localhost:5265/css/admin.css - null null +2026-07-12 01:52:47.380 +09:00 [INF] Sending file. Request path: '/css/admin.css'. Physical path: 'C:\Temp\data_feed\src\dotnet\QuantEngine.Web\wwwroot\css\admin.css' +2026-07-12 01:52:47.380 +09:00 [INF] HTTP GET /css/admin.css responded 200 in 2.4015 ms +2026-07-12 01:52:47.380 +09:00 [INF] Request finished HTTP/1.1 GET http://localhost:5265/css/admin.css - 200 3956 text/css 2.7681ms +2026-07-12 01:52:48.337 +09:00 [INF] Request starting HTTP/1.1 GET http://localhost:5265/Admin/Collection - null null +2026-07-12 01:52:48.338 +09:00 [INF] Executing endpoint '/Admin/Collection/Index' +2026-07-12 01:52:48.355 +09:00 [INF] Route matched with {page = "/Admin/Collection/Index"}. Executing page /Admin/Collection/Index +2026-07-12 01:52:48.355 +09:00 [INF] Executing handler method QuantEngine.Web.Pages.Admin.Collection.IndexModel.OnGetAsync - ModelState is "Valid" +2026-07-12 01:52:48.581 +09:00 [INF] Executed handler method OnGetAsync, returned result . +2026-07-12 01:52:48.581 +09:00 [INF] Executing an implicit handler method - ModelState is "Valid" +2026-07-12 01:52:48.581 +09:00 [INF] Executed an implicit handler method, returned result Microsoft.AspNetCore.Mvc.RazorPages.PageResult. +2026-07-12 01:52:48.591 +09:00 [INF] Executed page /Admin/Collection/Index in 236.0372ms +2026-07-12 01:52:48.591 +09:00 [INF] Executed endpoint '/Admin/Collection/Index' +2026-07-12 01:52:48.591 +09:00 [INF] HTTP GET /Admin/Collection responded 200 in 254.0162 ms +2026-07-12 01:52:48.592 +09:00 [INF] Request finished HTTP/1.1 GET http://localhost:5265/Admin/Collection - 200 null text/html; charset=utf-8 254.527ms +2026-07-12 01:52:49.211 +09:00 [INF] Request starting HTTP/1.1 GET http://localhost:5265/Admin/Monitoring - null null +2026-07-12 01:52:49.211 +09:00 [INF] Executing endpoint '/Admin/Monitoring/Index' +2026-07-12 01:52:49.217 +09:00 [INF] Route matched with {page = "/Admin/Monitoring/Index"}. Executing page /Admin/Monitoring/Index +2026-07-12 01:52:49.218 +09:00 [INF] Executing handler method QuantEngine.Web.Pages.Admin.Monitoring.IndexModel.OnGetAsync - ModelState is "Valid" +2026-07-12 01:52:49.440 +09:00 [INF] Executed handler method OnGetAsync, returned result . +2026-07-12 01:52:49.440 +09:00 [INF] Executing an implicit handler method - ModelState is "Valid" +2026-07-12 01:52:49.440 +09:00 [INF] Executed an implicit handler method, returned result Microsoft.AspNetCore.Mvc.RazorPages.PageResult. +2026-07-12 01:52:49.453 +09:00 [INF] Executed page /Admin/Monitoring/Index in 235.3992ms +2026-07-12 01:52:49.453 +09:00 [INF] Executed endpoint '/Admin/Monitoring/Index' +2026-07-12 01:52:49.453 +09:00 [INF] HTTP GET /Admin/Monitoring responded 200 in 242.4802 ms +2026-07-12 01:52:49.453 +09:00 [INF] Request finished HTTP/1.1 GET http://localhost:5265/Admin/Monitoring - 200 null text/html; charset=utf-8 242.724ms +2026-07-12 01:52:50.106 +09:00 [INF] Request starting HTTP/1.1 GET http://localhost:5265/Admin/Users - null null +2026-07-12 01:52:50.107 +09:00 [INF] Executing endpoint '/Admin/Users/Index' +2026-07-12 01:52:50.117 +09:00 [INF] Route matched with {page = "/Admin/Users/Index"}. Executing page /Admin/Users/Index +2026-07-12 01:52:50.118 +09:00 [INF] Executing handler method QuantEngine.Web.Pages.Admin.Users.IndexModel.OnGetAsync - ModelState is "Valid" +2026-07-12 01:52:50.339 +09:00 [INF] Executed handler method OnGetAsync, returned result . +2026-07-12 01:52:50.339 +09:00 [INF] Executing an implicit handler method - ModelState is "Valid" +2026-07-12 01:52:50.339 +09:00 [INF] Executed an implicit handler method, returned result Microsoft.AspNetCore.Mvc.RazorPages.PageResult. +2026-07-12 01:52:50.346 +09:00 [INF] Executed page /Admin/Users/Index in 228.3491ms +2026-07-12 01:52:50.346 +09:00 [INF] Executed endpoint '/Admin/Users/Index' +2026-07-12 01:52:50.346 +09:00 [INF] HTTP GET /Admin/Users responded 200 in 239.3317 ms +2026-07-12 01:52:50.346 +09:00 [INF] Request finished HTTP/1.1 GET http://localhost:5265/Admin/Users - 200 null text/html; charset=utf-8 239.5416ms +2026-07-12 01:56:32.591 +09:00 [INF] Registered 10 endpoints in 168 milliseconds. +2026-07-12 01:56:32.627 +09:00 [INF] 🔄 Starting database migration with DbUp... +2026-07-12 01:56:34.908 +09:00 [INF] ✅ Database migration completed successfully +2026-07-12 01:56:37.976 +09:00 [INF] Database migration and initialization successful +2026-07-12 01:56:38.000 +09:00 [INF] User profile is available. Using 'C:\Users\kjh20\AppData\Local\ASP.NET\DataProtection-Keys' as key repository and Windows DPAPI to encrypt keys at rest. +2026-07-12 01:56:38.043 +09:00 [INF] Start installing Hangfire SQL objects... +2026-07-12 01:56:42.666 +09:00 [INF] Hangfire SQL objects installed. +2026-07-12 01:56:42.681 +09:00 [INF] Initializing Hangfire schedules... +2026-07-12 01:56:50.823 +09:00 [INF] Hangfire schedules initialized successfully +2026-07-12 01:56:50.988 +09:00 [INF] Now listening on: http://localhost:5265 +2026-07-12 01:56:50.992 +09:00 [INF] Starting Hangfire Server using job storage: 'PostgreSQL Server: Host: 127.0.0.1, DB: quantenginedb, Schema: hangfire' +2026-07-12 01:56:50.992 +09:00 [INF] Using the following options for PostgreSQL job storage: +2026-07-12 01:56:50.992 +09:00 [INF] Queue poll interval: 00:00:15. +2026-07-12 01:56:50.992 +09:00 [INF] Invisibility timeout: 00:30:00. +2026-07-12 01:56:50.992 +09:00 [INF] Use sliding invisibility timeout: False. +2026-07-12 01:56:50.992 +09:00 [INF] Using the following options for Hangfire Server: + Worker count: 32 + Listening queues: 'default' + Shutdown timeout: 00:00:15 + Schedule polling interval: 00:00:15 +2026-07-12 01:56:51.000 +09:00 [INF] Application started. Press Ctrl+C to shut down. +2026-07-12 01:56:51.000 +09:00 [INF] Hosting environment: Development +2026-07-12 01:56:51.000 +09:00 [INF] Content root path: C:\Temp\data_feed\src\dotnet\QuantEngine.Web +2026-07-12 01:56:51.234 +09:00 [INF] Server kimjaehyun-note:18688:50858618 successfully announced in 231.3723 ms +2026-07-12 01:56:51.239 +09:00 [INF] Server kimjaehyun-note:18688:50858618 is starting the registered dispatchers: ServerWatchdog, ServerJobCancellationWatcher, ExpirationManager, CountersAggregator, Worker, DelayedJobScheduler, RecurringJobScheduler... +2026-07-12 01:56:51.251 +09:00 [INF] Server kimjaehyun-note:18688:50858618 all the dispatchers started +2026-07-12 01:56:52.918 +09:00 [INF] Request starting HTTP/1.1 GET http://localhost:5265/Account/Login - null null +2026-07-12 01:56:52.923 +09:00 [WRN] Failed to determine the https port for redirect. +2026-07-12 01:56:52.975 +09:00 [INF] Executing endpoint '/Account/Login' +2026-07-12 01:56:53.000 +09:00 [INF] Route matched with {page = "/Account/Login"}. Executing page /Account/Login +2026-07-12 01:56:53.008 +09:00 [INF] Executing handler method QuantEngine.Web.Pages.Account.LoginModel.OnGet - ModelState is "Valid" +2026-07-12 01:56:53.009 +09:00 [INF] Executed handler method OnGet, returned result . +2026-07-12 01:56:53.011 +09:00 [INF] Executing an implicit handler method - ModelState is "Valid" +2026-07-12 01:56:53.012 +09:00 [INF] Executed an implicit handler method, returned result Microsoft.AspNetCore.Mvc.RazorPages.PageResult. +2026-07-12 01:56:53.043 +09:00 [INF] Executed page /Account/Login in 38.7733ms +2026-07-12 01:56:53.043 +09:00 [INF] Executed endpoint '/Account/Login' +2026-07-12 01:56:53.045 +09:00 [INF] HTTP GET /Account/Login responded 200 in 123.2950 ms +2026-07-12 01:56:53.048 +09:00 [INF] Request finished HTTP/1.1 GET http://localhost:5265/Account/Login - 200 null text/html; charset=utf-8 131.2244ms +2026-07-12 10:29:03.142 +09:00 [INF] Registered 10 endpoints in 4,906 milliseconds. +2026-07-12 10:29:03.556 +09:00 [INF] 🔄 Starting database migration with DbUp... +2026-07-12 10:29:07.251 +09:00 [ERR] ❌ Database migration failed: 28P01: password authentication failed for user "quantengine_app" +2026-07-12 10:29:07.251 +09:00 [ERR] ❌ Database migration failed +System.InvalidOperationException: Database migration failed: 28P01: password authentication failed for user "quantengine_app" + at QuantEngine.Infrastructure.Data.DbMigrator.Migrate() in C:\Temp\data_feed\src\dotnet\QuantEngine.Infrastructure\Data\DbMigrator.cs:line 39 +2026-07-12 10:29:07.276 +09:00 [WRN] Database initialization warning (development only): Database migration failed: 28P01: password authentication failed for user "quantengine_app" +2026-07-12 10:29:07.442 +09:00 [INF] Initializing Hangfire schedules... +2026-07-12 10:29:07.565 +09:00 [INF] Hangfire schedules initialized successfully +2026-07-12 10:29:07.677 +09:00 [INF] Creating key {49191b44-5683-456a-b1ee-0ddbc2c7fc29} with creation date 2026-07-12 01:29:07Z, activation date 2026-07-12 01:29:07Z, and expiration date 2026-10-10 01:29:07Z. +2026-07-12 10:29:07.689 +09:00 [WRN] No XML encryptor configured. Key {49191b44-5683-456a-b1ee-0ddbc2c7fc29} may be persisted to storage in unencrypted form. +2026-07-12 10:29:07.697 +09:00 [INF] Writing data to file 'C:\Users\kjh20\AppData\Local\quantengine-keys\key-49191b44-5683-456a-b1ee-0ddbc2c7fc29.xml'. +2026-07-12 10:29:07.829 +09:00 [INF] Now listening on: http://localhost:5265 +2026-07-12 10:29:07.833 +09:00 [INF] Starting Hangfire Server using job storage: 'Hangfire.MemoryStorage.MemoryStorage' +2026-07-12 10:29:07.834 +09:00 [INF] Using the following options for Hangfire Server: + Worker count: 32 + Listening queues: 'default' + Shutdown timeout: 00:00:15 + Schedule polling interval: 00:00:15 +2026-07-12 10:29:07.843 +09:00 [INF] Application started. Press Ctrl+C to shut down. +2026-07-12 10:29:07.843 +09:00 [INF] Hosting environment: Development +2026-07-12 10:29:07.843 +09:00 [INF] Content root path: C:\Temp\data_feed\src\dotnet\QuantEngine.Web +2026-07-12 10:29:07.850 +09:00 [INF] Server kimjaehyun-note:27604:c5019131 successfully announced in 5.0644 ms +2026-07-12 10:29:07.852 +09:00 [INF] Server kimjaehyun-note:27604:c5019131 is starting the registered dispatchers: ServerWatchdog, ServerJobCancellationWatcher, ExpirationManager, CountersAggregator, Worker, DelayedJobScheduler, RecurringJobScheduler... +2026-07-12 10:29:07.863 +09:00 [INF] Server kimjaehyun-note:27604:c5019131 all the dispatchers started +2026-07-12 10:29:10.923 +09:00 [INF] Request starting HTTP/1.1 GET http://localhost:5265/Account/Login - null null +2026-07-12 10:29:10.929 +09:00 [WRN] Failed to determine the https port for redirect. +2026-07-12 10:29:10.969 +09:00 [INF] Executing endpoint '/Account/Login' +2026-07-12 10:29:10.987 +09:00 [INF] Route matched with {page = "/Account/Login"}. Executing page /Account/Login +2026-07-12 10:29:10.994 +09:00 [INF] Executing handler method QuantEngine.Web.Pages.Account.LoginModel.OnGet - ModelState is "Valid" +2026-07-12 10:29:10.995 +09:00 [INF] Executed handler method OnGet, returned result . +2026-07-12 10:29:10.997 +09:00 [INF] Executing an implicit handler method - ModelState is "Valid" +2026-07-12 10:29:10.998 +09:00 [INF] Executed an implicit handler method, returned result Microsoft.AspNetCore.Mvc.RazorPages.PageResult. +2026-07-12 10:29:11.026 +09:00 [INF] Executed page /Account/Login in 35.8464ms +2026-07-12 10:29:11.027 +09:00 [INF] Executed endpoint '/Account/Login' +2026-07-12 10:29:11.028 +09:00 [INF] HTTP GET /Account/Login responded 200 in 99.7375 ms +2026-07-12 10:29:11.030 +09:00 [INF] Request finished HTTP/1.1 GET http://localhost:5265/Account/Login - 200 null text/html; charset=utf-8 121.6214ms +2026-07-12 10:54:40.138 +09:00 [INF] Request starting HTTP/1.1 GET http://localhost:5265/login - null null +2026-07-12 10:54:40.144 +09:00 [INF] Executing endpoint 'HTTP: GET /login' +2026-07-12 10:54:40.145 +09:00 [INF] Executed endpoint 'HTTP: GET /login' +2026-07-12 10:54:40.145 +09:00 [INF] HTTP GET /login responded 302 in 6.0857 ms +2026-07-12 10:54:40.146 +09:00 [INF] Request finished HTTP/1.1 GET http://localhost:5265/login - 302 0 null 9.206ms +2026-07-12 10:54:40.153 +09:00 [INF] Request starting HTTP/1.1 GET http://localhost:5265/Account/Login - null null +2026-07-12 10:54:40.163 +09:00 [INF] Executing endpoint '/Account/Login' +2026-07-12 10:54:40.163 +09:00 [INF] Route matched with {page = "/Account/Login"}. Executing page /Account/Login +2026-07-12 10:54:40.165 +09:00 [INF] Executing handler method QuantEngine.Web.Pages.Account.LoginModel.OnGet - ModelState is "Valid" +2026-07-12 10:54:40.165 +09:00 [INF] Executed handler method OnGet, returned result . +2026-07-12 10:54:40.165 +09:00 [INF] Executing an implicit handler method - ModelState is "Valid" +2026-07-12 10:54:40.166 +09:00 [INF] Executed an implicit handler method, returned result Microsoft.AspNetCore.Mvc.RazorPages.PageResult. +2026-07-12 10:54:40.179 +09:00 [INF] Executed page /Account/Login in 15.2716ms +2026-07-12 10:54:40.179 +09:00 [INF] Executed endpoint '/Account/Login' +2026-07-12 10:54:40.179 +09:00 [INF] HTTP GET /Account/Login responded 200 in 25.4427 ms +2026-07-12 10:54:40.180 +09:00 [INF] Request finished HTTP/1.1 GET http://localhost:5265/Account/Login - 200 null text/html; charset=utf-8 26.1015ms +2026-07-12 10:54:40.185 +09:00 [INF] Request starting HTTP/1.1 GET http://localhost:5265/login - null null +2026-07-12 10:54:40.186 +09:00 [INF] Executing endpoint 'HTTP: GET /login' +2026-07-12 10:54:40.187 +09:00 [INF] Executed endpoint 'HTTP: GET /login' +2026-07-12 10:54:40.187 +09:00 [INF] HTTP GET /login responded 302 in 1.4987 ms +2026-07-12 10:54:40.187 +09:00 [INF] Request finished HTTP/1.1 GET http://localhost:5265/login - 302 0 null 2.2724ms +2026-07-12 10:54:40.190 +09:00 [INF] Request starting HTTP/1.1 GET http://localhost:5265/Account/Login - null null +2026-07-12 10:54:40.191 +09:00 [INF] Executing endpoint '/Account/Login' +2026-07-12 10:54:40.191 +09:00 [INF] Route matched with {page = "/Account/Login"}. Executing page /Account/Login +2026-07-12 10:54:40.195 +09:00 [INF] Executing handler method QuantEngine.Web.Pages.Account.LoginModel.OnGet - ModelState is "Valid" +2026-07-12 10:54:40.196 +09:00 [INF] Executed handler method OnGet, returned result . +2026-07-12 10:54:40.196 +09:00 [INF] Executing an implicit handler method - ModelState is "Valid" +2026-07-12 10:54:40.196 +09:00 [INF] Executed an implicit handler method, returned result Microsoft.AspNetCore.Mvc.RazorPages.PageResult. +2026-07-12 10:54:40.203 +09:00 [INF] Executed page /Account/Login in 11.1274ms +2026-07-12 10:54:40.203 +09:00 [INF] Executed endpoint '/Account/Login' +2026-07-12 10:54:40.203 +09:00 [INF] HTTP GET /Account/Login responded 200 in 12.4260 ms +2026-07-12 10:54:40.203 +09:00 [INF] Request finished HTTP/1.1 GET http://localhost:5265/Account/Login - 200 null text/html; charset=utf-8 13.0096ms +2026-07-12 10:54:44.086 +09:00 [INF] Request starting HTTP/1.1 GET http://localhost:5265/Account/Login - null null +2026-07-12 10:54:44.087 +09:00 [INF] Executing endpoint '/Account/Login' +2026-07-12 10:54:44.087 +09:00 [INF] Route matched with {page = "/Account/Login"}. Executing page /Account/Login +2026-07-12 10:54:44.087 +09:00 [INF] Executing handler method QuantEngine.Web.Pages.Account.LoginModel.OnGet - ModelState is "Valid" +2026-07-12 10:54:44.087 +09:00 [INF] Executed handler method OnGet, returned result . +2026-07-12 10:54:44.088 +09:00 [INF] Executing an implicit handler method - ModelState is "Valid" +2026-07-12 10:54:44.088 +09:00 [INF] Executed an implicit handler method, returned result Microsoft.AspNetCore.Mvc.RazorPages.PageResult. +2026-07-12 10:54:44.088 +09:00 [INF] Executed page /Account/Login in 1.1538ms +2026-07-12 10:54:44.089 +09:00 [INF] Executed endpoint '/Account/Login' +2026-07-12 10:54:44.089 +09:00 [INF] HTTP GET /Account/Login responded 200 in 1.8741 ms +2026-07-12 10:54:44.089 +09:00 [INF] Request finished HTTP/1.1 GET http://localhost:5265/Account/Login - 200 null text/html; charset=utf-8 2.4044ms +2026-07-12 10:54:44.647 +09:00 [INF] Request starting HTTP/1.1 POST http://localhost:5265/Account/Login - application/x-www-form-urlencoded 212 +2026-07-12 10:54:44.649 +09:00 [INF] Executing endpoint '/Account/Login' +2026-07-12 10:54:44.649 +09:00 [INF] Route matched with {page = "/Account/Login"}. Executing page /Account/Login +2026-07-12 10:54:44.686 +09:00 [INF] Executing handler method QuantEngine.Web.Pages.Account.LoginModel.OnPostAsync - ModelState is "Valid" +2026-07-12 10:54:48.980 +09:00 [INF] Executed page /Account/Login in 4330.4506ms +2026-07-12 10:54:48.981 +09:00 [INF] Executed endpoint '/Account/Login' +2026-07-12 10:54:48.984 +09:00 [ERR] HTTP POST /Account/Login responded 500 in 4336.7156 ms +Npgsql.PostgresException (0x80004005): 28P01: password authentication failed for user "quantengine_app" + at Npgsql.Internal.NpgsqlConnector.ReadMessageLong(Boolean async, DataRowLoadingMode dataRowLoadingMode, Boolean readingNotifications, Boolean isReadingPrependedMessage) + at System.Runtime.CompilerServices.PoolingAsyncValueTaskMethodBuilder`1.StateMachineBox`1.System.Threading.Tasks.Sources.IValueTaskSource.GetResult(Int16 token) + at Npgsql.Internal.NpgsqlConnector.AuthenticateSASL(List`1 mechanisms, String username, Boolean async, CancellationToken cancellationToken) + at Npgsql.Internal.NpgsqlConnector.Authenticate(String username, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken) + at Npgsql.Internal.NpgsqlConnector.g__OpenCore|209_0(NpgsqlConnector conn, String username, SslMode sslMode, GssEncryptionMode gssEncMode, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken) + at Npgsql.Internal.NpgsqlConnector.g__OpenCore|209_0(NpgsqlConnector conn, String username, SslMode sslMode, GssEncryptionMode gssEncMode, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken) + at Npgsql.Internal.NpgsqlConnector.g__OpenCore|209_0(NpgsqlConnector conn, String username, SslMode sslMode, GssEncryptionMode gssEncMode, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken) + at Npgsql.Internal.NpgsqlConnector.Open(NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken) + at Npgsql.PoolingDataSource.OpenNewConnector(NpgsqlConnection conn, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken) + at Npgsql.PoolingDataSource.g__RentAsync|33_0(NpgsqlConnection conn, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken) + at Npgsql.NpgsqlConnection.g__OpenAsync|42_0(Boolean async, CancellationToken cancellationToken) + at Dapper.SqlMapper.QueryRowAsync[T](IDbConnection cnn, Row row, Type effectiveType, CommandDefinition command) in /_/Dapper/SqlMapper.Async.cs:line 488 + at QuantEngine.Infrastructure.Repositories.WorkspaceRepository.GetAccountByUsernameAsync(String username) in C:\Temp\data_feed\src\dotnet\QuantEngine.Infrastructure\Repositories\WorkspaceRepository.cs:line 35 + at QuantEngine.Web.Services.AuthService.AuthenticateAsync(String username, String password, String ipAddress) in C:\Temp\data_feed\src\dotnet\QuantEngine.Web\Services\AuthService.cs:line 26 + at QuantEngine.Web.Pages.Account.LoginModel.OnPostAsync(String username, String password, Boolean rememberUsername) in C:\Temp\data_feed\src\dotnet\QuantEngine.Web\Pages\Account\Login.cshtml.cs:line 49 + at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.ExecutorFactory.GenericTaskHandlerMethod.Convert[T](Object taskAsObject) + at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.ExecutorFactory.GenericTaskHandlerMethod.Execute(Object receiver, Object[] arguments) + at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.InvokeHandlerMethodAsync() + at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.InvokeNextPageFilterAsync() + at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.Rethrow(PageHandlerExecutedContext context) + at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) + at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.InvokeInnerFilterAsync() + at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) + at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context) + at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) + at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) + at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Logged|17_1(ResourceInvoker invoker) + at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Logged|17_1(ResourceInvoker invoker) + at Microsoft.AspNetCore.Routing.EndpointMiddleware.g__AwaitRequestTask|7_0(Endpoint endpoint, Task requestTask, ILogger logger) + at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context) + at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context) + at Serilog.AspNetCore.RequestLoggingMiddleware.Invoke(HttpContext httpContext) + Exception data: + Severity: FATAL + SqlState: 28P01 + MessageText: password authentication failed for user "quantengine_app" + File: auth.c + Line: 317 + Routine: auth_failed +2026-07-12 10:54:49.034 +09:00 [ERR] An unhandled exception has occurred while executing the request. +Npgsql.PostgresException (0x80004005): 28P01: password authentication failed for user "quantengine_app" + at Npgsql.Internal.NpgsqlConnector.ReadMessageLong(Boolean async, DataRowLoadingMode dataRowLoadingMode, Boolean readingNotifications, Boolean isReadingPrependedMessage) + at System.Runtime.CompilerServices.PoolingAsyncValueTaskMethodBuilder`1.StateMachineBox`1.System.Threading.Tasks.Sources.IValueTaskSource.GetResult(Int16 token) + at Npgsql.Internal.NpgsqlConnector.AuthenticateSASL(List`1 mechanisms, String username, Boolean async, CancellationToken cancellationToken) + at Npgsql.Internal.NpgsqlConnector.Authenticate(String username, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken) + at Npgsql.Internal.NpgsqlConnector.g__OpenCore|209_0(NpgsqlConnector conn, String username, SslMode sslMode, GssEncryptionMode gssEncMode, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken) + at Npgsql.Internal.NpgsqlConnector.g__OpenCore|209_0(NpgsqlConnector conn, String username, SslMode sslMode, GssEncryptionMode gssEncMode, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken) + at Npgsql.Internal.NpgsqlConnector.g__OpenCore|209_0(NpgsqlConnector conn, String username, SslMode sslMode, GssEncryptionMode gssEncMode, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken) + at Npgsql.Internal.NpgsqlConnector.Open(NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken) + at Npgsql.PoolingDataSource.OpenNewConnector(NpgsqlConnection conn, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken) + at Npgsql.PoolingDataSource.g__RentAsync|33_0(NpgsqlConnection conn, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken) + at Npgsql.NpgsqlConnection.g__OpenAsync|42_0(Boolean async, CancellationToken cancellationToken) + at Dapper.SqlMapper.QueryRowAsync[T](IDbConnection cnn, Row row, Type effectiveType, CommandDefinition command) in /_/Dapper/SqlMapper.Async.cs:line 488 + at QuantEngine.Infrastructure.Repositories.WorkspaceRepository.GetAccountByUsernameAsync(String username) in C:\Temp\data_feed\src\dotnet\QuantEngine.Infrastructure\Repositories\WorkspaceRepository.cs:line 35 + at QuantEngine.Web.Services.AuthService.AuthenticateAsync(String username, String password, String ipAddress) in C:\Temp\data_feed\src\dotnet\QuantEngine.Web\Services\AuthService.cs:line 26 + at QuantEngine.Web.Pages.Account.LoginModel.OnPostAsync(String username, String password, Boolean rememberUsername) in C:\Temp\data_feed\src\dotnet\QuantEngine.Web\Pages\Account\Login.cshtml.cs:line 49 + at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.ExecutorFactory.GenericTaskHandlerMethod.Convert[T](Object taskAsObject) + at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.ExecutorFactory.GenericTaskHandlerMethod.Execute(Object receiver, Object[] arguments) + at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.InvokeHandlerMethodAsync() + at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.InvokeNextPageFilterAsync() + at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.Rethrow(PageHandlerExecutedContext context) + at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) + at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.InvokeInnerFilterAsync() + at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) + at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context) + at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) + at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) + at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Logged|17_1(ResourceInvoker invoker) + at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Logged|17_1(ResourceInvoker invoker) + at Microsoft.AspNetCore.Routing.EndpointMiddleware.g__AwaitRequestTask|7_0(Endpoint endpoint, Task requestTask, ILogger logger) + at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context) + at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context) + at Serilog.AspNetCore.RequestLoggingMiddleware.Invoke(HttpContext httpContext) + at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context) + Exception data: + Severity: FATAL + SqlState: 28P01 + MessageText: password authentication failed for user "quantengine_app" + File: auth.c + Line: 317 + Routine: auth_failed +2026-07-12 10:54:49.111 +09:00 [INF] Request finished HTTP/1.1 POST http://localhost:5265/Account/Login - 500 null text/html; charset=utf-8 4464.5985ms +2026-07-12 10:54:49.205 +09:00 [INF] Request starting HTTP/1.1 GET http://localhost:5265/api/collection/runs?limit=20 - null null +2026-07-12 10:54:49.207 +09:00 [INF] Executing endpoint 'HTTP: GET /api/collection/runs' +2026-07-12 10:54:52.982 +09:00 [INF] Executed endpoint 'HTTP: GET /api/collection/runs' +2026-07-12 10:54:52.982 +09:00 [ERR] HTTP GET /api/collection/runs responded 500 in 3777.5166 ms +2026-07-12 10:54:52.983 +09:00 [INF] Request finished HTTP/1.1 GET http://localhost:5265/api/collection/runs?limit=20 - 500 null application/problem+json; charset=utf-8 3778.0479ms +2026-07-12 13:02:07.505 +09:00 [INF] Request starting HTTP/1.1 GET http://localhost:5265/login - null null +2026-07-12 13:02:07.523 +09:00 [INF] Executing endpoint 'HTTP: GET /login' +2026-07-12 13:02:07.523 +09:00 [INF] Executed endpoint 'HTTP: GET /login' +2026-07-12 13:02:07.524 +09:00 [INF] HTTP GET /login responded 302 in 16.0250 ms +2026-07-12 13:02:07.529 +09:00 [INF] Request finished HTTP/1.1 GET http://localhost:5265/login - 302 0 null 21.1915ms +2026-07-12 13:02:07.531 +09:00 [INF] Request starting HTTP/1.1 GET http://localhost:5265/Account/Login - null null +2026-07-12 13:02:07.531 +09:00 [INF] Executing endpoint '/Account/Login' +2026-07-12 13:02:07.540 +09:00 [INF] Route matched with {page = "/Account/Login"}. Executing page /Account/Login +2026-07-12 13:02:07.546 +09:00 [INF] Executing handler method QuantEngine.Web.Pages.Account.LoginModel.OnGet - ModelState is "Valid" +2026-07-12 13:02:07.548 +09:00 [INF] Executed handler method OnGet, returned result . +2026-07-12 13:02:07.548 +09:00 [INF] Executing an implicit handler method - ModelState is "Valid" +2026-07-12 13:02:07.550 +09:00 [INF] Executed an implicit handler method, returned result Microsoft.AspNetCore.Mvc.RazorPages.PageResult. +2026-07-12 13:02:07.581 +09:00 [INF] Executed page /Account/Login in 40.445ms +2026-07-12 13:02:07.581 +09:00 [INF] Executed endpoint '/Account/Login' +2026-07-12 13:02:07.581 +09:00 [INF] HTTP GET /Account/Login responded 200 in 49.9978 ms +2026-07-12 13:02:07.581 +09:00 [INF] Request finished HTTP/1.1 GET http://localhost:5265/Account/Login - 200 null text/html; charset=utf-8 50.4194ms +2026-07-12 13:02:07.585 +09:00 [INF] Request starting HTTP/1.1 GET http://localhost:5265/login - null null +2026-07-12 13:02:07.585 +09:00 [INF] Executing endpoint 'HTTP: GET /login' +2026-07-12 13:02:07.585 +09:00 [INF] Executed endpoint 'HTTP: GET /login' +2026-07-12 13:02:07.585 +09:00 [INF] HTTP GET /login responded 302 in 0.3455 ms +2026-07-12 13:02:07.585 +09:00 [INF] Request finished HTTP/1.1 GET http://localhost:5265/login - 302 0 null 0.6015ms +2026-07-12 13:02:07.586 +09:00 [INF] Request starting HTTP/1.1 GET http://localhost:5265/Account/Login - null null +2026-07-12 13:02:07.587 +09:00 [INF] Executing endpoint '/Account/Login' +2026-07-12 13:02:07.587 +09:00 [INF] Route matched with {page = "/Account/Login"}. Executing page /Account/Login +2026-07-12 13:02:07.587 +09:00 [INF] Executing handler method QuantEngine.Web.Pages.Account.LoginModel.OnGet - ModelState is "Valid" +2026-07-12 13:02:07.587 +09:00 [INF] Executed handler method OnGet, returned result . +2026-07-12 13:02:07.588 +09:00 [INF] Executing an implicit handler method - ModelState is "Valid" +2026-07-12 13:02:07.588 +09:00 [INF] Executed an implicit handler method, returned result Microsoft.AspNetCore.Mvc.RazorPages.PageResult. +2026-07-12 13:02:07.589 +09:00 [INF] Executed page /Account/Login in 1.3984ms +2026-07-12 13:02:07.589 +09:00 [INF] Executed endpoint '/Account/Login' +2026-07-12 13:02:07.589 +09:00 [INF] HTTP GET /Account/Login responded 200 in 2.2170 ms +2026-07-12 13:02:07.589 +09:00 [INF] Request finished HTTP/1.1 GET http://localhost:5265/Account/Login - 200 null text/html; charset=utf-8 2.4854ms +2026-07-12 13:02:11.141 +09:00 [INF] Request starting HTTP/1.1 GET http://localhost:5265/Account/Login - null null +2026-07-12 13:02:11.142 +09:00 [INF] Executing endpoint '/Account/Login' +2026-07-12 13:02:11.142 +09:00 [INF] Route matched with {page = "/Account/Login"}. Executing page /Account/Login +2026-07-12 13:02:11.142 +09:00 [INF] Executing handler method QuantEngine.Web.Pages.Account.LoginModel.OnGet - ModelState is "Valid" +2026-07-12 13:02:11.143 +09:00 [INF] Executed handler method OnGet, returned result . +2026-07-12 13:02:11.143 +09:00 [INF] Executing an implicit handler method - ModelState is "Valid" +2026-07-12 13:02:11.143 +09:00 [INF] Executed an implicit handler method, returned result Microsoft.AspNetCore.Mvc.RazorPages.PageResult. +2026-07-12 13:02:11.144 +09:00 [INF] Executed page /Account/Login in 1.7154ms +2026-07-12 13:02:11.144 +09:00 [INF] Executed endpoint '/Account/Login' +2026-07-12 13:02:11.144 +09:00 [INF] HTTP GET /Account/Login responded 200 in 2.8689 ms +2026-07-12 13:02:11.144 +09:00 [INF] Request finished HTTP/1.1 GET http://localhost:5265/Account/Login - 200 null text/html; charset=utf-8 3.2765ms +2026-07-12 13:02:11.640 +09:00 [INF] Request starting HTTP/1.1 POST http://localhost:5265/Account/Login - application/x-www-form-urlencoded 212 +2026-07-12 13:02:11.643 +09:00 [INF] Executing endpoint '/Account/Login' +2026-07-12 13:02:11.644 +09:00 [INF] Route matched with {page = "/Account/Login"}. Executing page /Account/Login +2026-07-12 13:02:11.684 +09:00 [INF] Executing handler method QuantEngine.Web.Pages.Account.LoginModel.OnPostAsync - ModelState is "Valid" +2026-07-12 13:02:15.491 +09:00 [INF] Executed page /Account/Login in 3846.9073ms +2026-07-12 13:02:15.493 +09:00 [INF] Executed endpoint '/Account/Login' +2026-07-12 13:02:15.495 +09:00 [ERR] HTTP POST /Account/Login responded 500 in 3854.1437 ms +Npgsql.PostgresException (0x80004005): 28P01: password authentication failed for user "quantengine_app" + at Npgsql.Internal.NpgsqlConnector.ReadMessageLong(Boolean async, DataRowLoadingMode dataRowLoadingMode, Boolean readingNotifications, Boolean isReadingPrependedMessage) + at System.Runtime.CompilerServices.PoolingAsyncValueTaskMethodBuilder`1.StateMachineBox`1.System.Threading.Tasks.Sources.IValueTaskSource.GetResult(Int16 token) + at Npgsql.Internal.NpgsqlConnector.AuthenticateSASL(List`1 mechanisms, String username, Boolean async, CancellationToken cancellationToken) + at Npgsql.Internal.NpgsqlConnector.Authenticate(String username, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken) + at Npgsql.Internal.NpgsqlConnector.g__OpenCore|209_0(NpgsqlConnector conn, String username, SslMode sslMode, GssEncryptionMode gssEncMode, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken) + at Npgsql.Internal.NpgsqlConnector.g__OpenCore|209_0(NpgsqlConnector conn, String username, SslMode sslMode, GssEncryptionMode gssEncMode, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken) + at Npgsql.Internal.NpgsqlConnector.g__OpenCore|209_0(NpgsqlConnector conn, String username, SslMode sslMode, GssEncryptionMode gssEncMode, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken) + at Npgsql.Internal.NpgsqlConnector.Open(NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken) + at Npgsql.PoolingDataSource.OpenNewConnector(NpgsqlConnection conn, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken) + at Npgsql.PoolingDataSource.g__RentAsync|33_0(NpgsqlConnection conn, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken) + at Npgsql.NpgsqlConnection.g__OpenAsync|42_0(Boolean async, CancellationToken cancellationToken) + at Dapper.SqlMapper.QueryRowAsync[T](IDbConnection cnn, Row row, Type effectiveType, CommandDefinition command) in /_/Dapper/SqlMapper.Async.cs:line 488 + at QuantEngine.Infrastructure.Repositories.WorkspaceRepository.GetAccountByUsernameAsync(String username) in C:\Temp\data_feed\src\dotnet\QuantEngine.Infrastructure\Repositories\WorkspaceRepository.cs:line 35 + at QuantEngine.Web.Services.AuthService.AuthenticateAsync(String username, String password, String ipAddress) in C:\Temp\data_feed\src\dotnet\QuantEngine.Web\Services\AuthService.cs:line 26 + at QuantEngine.Web.Pages.Account.LoginModel.OnPostAsync(String username, String password, Boolean rememberUsername) in C:\Temp\data_feed\src\dotnet\QuantEngine.Web\Pages\Account\Login.cshtml.cs:line 49 + at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.ExecutorFactory.GenericTaskHandlerMethod.Convert[T](Object taskAsObject) + at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.ExecutorFactory.GenericTaskHandlerMethod.Execute(Object receiver, Object[] arguments) + at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.InvokeHandlerMethodAsync() + at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.InvokeNextPageFilterAsync() + at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.Rethrow(PageHandlerExecutedContext context) + at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) + at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.InvokeInnerFilterAsync() + at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) + at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context) + at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) + at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) + at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Logged|17_1(ResourceInvoker invoker) + at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Logged|17_1(ResourceInvoker invoker) + at Microsoft.AspNetCore.Routing.EndpointMiddleware.g__AwaitRequestTask|7_0(Endpoint endpoint, Task requestTask, ILogger logger) + at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context) + at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context) + at Serilog.AspNetCore.RequestLoggingMiddleware.Invoke(HttpContext httpContext) + Exception data: + Severity: FATAL + SqlState: 28P01 + MessageText: password authentication failed for user "quantengine_app" + File: auth.c + Line: 317 + Routine: auth_failed +2026-07-12 13:02:15.530 +09:00 [ERR] An unhandled exception has occurred while executing the request. +Npgsql.PostgresException (0x80004005): 28P01: password authentication failed for user "quantengine_app" + at Npgsql.Internal.NpgsqlConnector.ReadMessageLong(Boolean async, DataRowLoadingMode dataRowLoadingMode, Boolean readingNotifications, Boolean isReadingPrependedMessage) + at System.Runtime.CompilerServices.PoolingAsyncValueTaskMethodBuilder`1.StateMachineBox`1.System.Threading.Tasks.Sources.IValueTaskSource.GetResult(Int16 token) + at Npgsql.Internal.NpgsqlConnector.AuthenticateSASL(List`1 mechanisms, String username, Boolean async, CancellationToken cancellationToken) + at Npgsql.Internal.NpgsqlConnector.Authenticate(String username, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken) + at Npgsql.Internal.NpgsqlConnector.g__OpenCore|209_0(NpgsqlConnector conn, String username, SslMode sslMode, GssEncryptionMode gssEncMode, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken) + at Npgsql.Internal.NpgsqlConnector.g__OpenCore|209_0(NpgsqlConnector conn, String username, SslMode sslMode, GssEncryptionMode gssEncMode, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken) + at Npgsql.Internal.NpgsqlConnector.g__OpenCore|209_0(NpgsqlConnector conn, String username, SslMode sslMode, GssEncryptionMode gssEncMode, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken) + at Npgsql.Internal.NpgsqlConnector.Open(NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken) + at Npgsql.PoolingDataSource.OpenNewConnector(NpgsqlConnection conn, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken) + at Npgsql.PoolingDataSource.g__RentAsync|33_0(NpgsqlConnection conn, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken) + at Npgsql.NpgsqlConnection.g__OpenAsync|42_0(Boolean async, CancellationToken cancellationToken) + at Dapper.SqlMapper.QueryRowAsync[T](IDbConnection cnn, Row row, Type effectiveType, CommandDefinition command) in /_/Dapper/SqlMapper.Async.cs:line 488 + at QuantEngine.Infrastructure.Repositories.WorkspaceRepository.GetAccountByUsernameAsync(String username) in C:\Temp\data_feed\src\dotnet\QuantEngine.Infrastructure\Repositories\WorkspaceRepository.cs:line 35 + at QuantEngine.Web.Services.AuthService.AuthenticateAsync(String username, String password, String ipAddress) in C:\Temp\data_feed\src\dotnet\QuantEngine.Web\Services\AuthService.cs:line 26 + at QuantEngine.Web.Pages.Account.LoginModel.OnPostAsync(String username, String password, Boolean rememberUsername) in C:\Temp\data_feed\src\dotnet\QuantEngine.Web\Pages\Account\Login.cshtml.cs:line 49 + at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.ExecutorFactory.GenericTaskHandlerMethod.Convert[T](Object taskAsObject) + at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.ExecutorFactory.GenericTaskHandlerMethod.Execute(Object receiver, Object[] arguments) + at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.InvokeHandlerMethodAsync() + at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.InvokeNextPageFilterAsync() + at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.Rethrow(PageHandlerExecutedContext context) + at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) + at Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.InvokeInnerFilterAsync() + at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) + at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context) + at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) + at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) + at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Logged|17_1(ResourceInvoker invoker) + at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Logged|17_1(ResourceInvoker invoker) + at Microsoft.AspNetCore.Routing.EndpointMiddleware.g__AwaitRequestTask|7_0(Endpoint endpoint, Task requestTask, ILogger logger) + at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context) + at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context) + at Serilog.AspNetCore.RequestLoggingMiddleware.Invoke(HttpContext httpContext) + at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context) + Exception data: + Severity: FATAL + SqlState: 28P01 + MessageText: password authentication failed for user "quantengine_app" + File: auth.c + Line: 317 + Routine: auth_failed +2026-07-12 13:02:15.576 +09:00 [INF] Request finished HTTP/1.1 POST http://localhost:5265/Account/Login - 500 null text/html; charset=utf-8 3935.4857ms +2026-07-12 13:02:15.664 +09:00 [INF] Request starting HTTP/1.1 GET http://localhost:5265/api/collection/runs?limit=20 - null null +2026-07-12 13:02:15.668 +09:00 [INF] Executing endpoint 'HTTP: GET /api/collection/runs' +2026-07-12 13:02:19.317 +09:00 [INF] Executed endpoint 'HTTP: GET /api/collection/runs' +2026-07-12 13:02:19.318 +09:00 [ERR] HTTP GET /api/collection/runs responded 500 in 3653.6595 ms +2026-07-12 13:02:19.318 +09:00 [INF] Request finished HTTP/1.1 GET http://localhost:5265/api/collection/runs?limit=20 - 500 null application/problem+json; charset=utf-8 3654.0261ms diff --git a/src/quant_engine/snapshot_admin.db b/src/quant_engine/snapshot_admin.db index 035c9b05..45e68d12 100644 Binary files a/src/quant_engine/snapshot_admin.db and b/src/quant_engine/snapshot_admin.db differ diff --git a/test-results/.last-run.json b/test-results/.last-run.json index cbcc1fba..4cc0eac8 100644 --- a/test-results/.last-run.json +++ b/test-results/.last-run.json @@ -1,4 +1,6 @@ { - "status": "passed", - "failedTests": [] + "status": "failed", + "failedTests": [ + "90c6053e24d905f92d61-fe6c7d0382f9666a596d" + ] } \ No newline at end of file diff --git a/test-results/qe-m1-02-collection-run-QE-ae870-API-derived-expected-values-evidence/error-context.md b/test-results/qe-m1-02-collection-run-QE-ae870-API-derived-expected-values-evidence/error-context.md new file mode 100644 index 00000000..5ce36b02 --- /dev/null +++ b/test-results/qe-m1-02-collection-run-QE-ae870-API-derived-expected-values-evidence/error-context.md @@ -0,0 +1,259 @@ +# Instructions + +- Following Playwright test failed. +- Explain why, be concise, respect Playwright best practices. +- Provide a snippet of code with the fix, if possible. + +# Test info + +- Name: evidence\qe-m1-02-collection-run.spec.ts >> QE-M1-02: Collection Run List & Detail Verification >> QE-M1-02: Collection run renders in list with API-derived expected values +- Location: tests\e2e\evidence\qe-m1-02-collection-run.spec.ts:24:3 + +# Error details + +``` +Error: expect(received).toBeTruthy() + +Received: false +``` + +# Page snapshot + +```yaml +- generic [active] [ref=e1]: + - heading "An unhandled exception occurred while processing the request." [level=1] [ref=e2] + - generic [ref=e3]: "PostgresException: 28P01: password authentication failed for user \"quantengine_app\"" + - paragraph [ref=e4]: Npgsql.Internal.NpgsqlConnector.ReadMessageLong(bool async, DataRowLoadingMode dataRowLoadingMode, bool readingNotifications, bool isReadingPrependedMessage) + - list [ref=e5]: + - listitem [ref=e6] [cursor=pointer]: Stack + - listitem [ref=e7] [cursor=pointer]: Query + - listitem [ref=e8] [cursor=pointer]: Cookies + - listitem [ref=e9] [cursor=pointer]: Headers + - listitem [ref=e10] [cursor=pointer]: Routing + - list [ref=e12]: + - listitem [ref=e13]: + - 'heading "PostgresException: 28P01: password authentication failed for user \"quantengine_app\"" [level=2] [ref=e14]' + - list [ref=e15]: + - listitem [ref=e16]: + - heading "Npgsql.Internal.NpgsqlConnector.ReadMessageLong(bool async, DataRowLoadingMode dataRowLoadingMode, bool readingNotifications, bool isReadingPrependedMessage)" [level=3] [ref=e17] + - listitem [ref=e18]: + - heading "System.Runtime.CompilerServices.PoolingAsyncValueTaskMethodBuilder+StateMachineBox.System.Threading.Tasks.Sources.IValueTaskSource.GetResult(short token)" [level=3] [ref=e19] + - listitem [ref=e20]: + - heading "Npgsql.Internal.NpgsqlConnector.AuthenticateSASL(List mechanisms, string username, bool async, CancellationToken cancellationToken)" [level=3] [ref=e21] + - listitem [ref=e22]: + - heading "Npgsql.Internal.NpgsqlConnector.Authenticate(string username, NpgsqlTimeout timeout, bool async, CancellationToken cancellationToken)" [level=3] [ref=e23] + - listitem [ref=e24]: + - heading "Npgsql.Internal.NpgsqlConnector.g__OpenCore|209_0(NpgsqlConnector conn, string username, SslMode sslMode, GssEncryptionMode gssEncMode, NpgsqlTimeout timeout, bool async, CancellationToken cancellationToken)" [level=3] [ref=e25] + - listitem [ref=e26]: + - heading "Npgsql.Internal.NpgsqlConnector.g__OpenCore|209_0(NpgsqlConnector conn, string username, SslMode sslMode, GssEncryptionMode gssEncMode, NpgsqlTimeout timeout, bool async, CancellationToken cancellationToken)" [level=3] [ref=e27] + - listitem [ref=e28]: + - heading "Npgsql.Internal.NpgsqlConnector.g__OpenCore|209_0(NpgsqlConnector conn, string username, SslMode sslMode, GssEncryptionMode gssEncMode, NpgsqlTimeout timeout, bool async, CancellationToken cancellationToken)" [level=3] [ref=e29] + - listitem [ref=e30]: + - heading "Npgsql.Internal.NpgsqlConnector.Open(NpgsqlTimeout timeout, bool async, CancellationToken cancellationToken)" [level=3] [ref=e31] + - listitem [ref=e32]: + - heading "Npgsql.PoolingDataSource.OpenNewConnector(NpgsqlConnection conn, NpgsqlTimeout timeout, bool async, CancellationToken cancellationToken)" [level=3] [ref=e33] + - listitem [ref=e34]: + - heading "System.Runtime.CompilerServices.ConfiguredValueTaskAwaitable+ConfiguredValueTaskAwaiter.GetResult()" [level=3] [ref=e35] + - listitem [ref=e36]: + - heading "Npgsql.PoolingDataSource.g__RentAsync|33_0(NpgsqlConnection conn, NpgsqlTimeout timeout, bool async, CancellationToken cancellationToken)" [level=3] [ref=e37] + - listitem [ref=e38]: + - heading "System.Runtime.CompilerServices.ConfiguredValueTaskAwaitable+ConfiguredValueTaskAwaiter.GetResult()" [level=3] [ref=e39] + - listitem [ref=e40]: + - heading "Npgsql.NpgsqlConnection.g__OpenAsync|42_0(bool async, CancellationToken cancellationToken)" [level=3] [ref=e41] + - listitem [ref=e42]: + - heading "Dapper.SqlMapper.QueryRowAsync(IDbConnection cnn, Row row, Type effectiveType, CommandDefinition command) in SqlMapper.Async.cs" [level=3] [ref=e43]: + - text: Dapper.SqlMapper.QueryRowAsync(IDbConnection cnn, Row row, Type effectiveType, CommandDefinition command) in + - code [ref=e44]: SqlMapper.Async.cs + - listitem [ref=e45]: + - heading "QuantEngine.Infrastructure.Repositories.WorkspaceRepository.GetAccountByUsernameAsync(string username) in WorkspaceRepository.cs" [level=3] [ref=e46]: + - text: QuantEngine.Infrastructure.Repositories.WorkspaceRepository.GetAccountByUsernameAsync(string username) in + - code [ref=e47]: WorkspaceRepository.cs + - button "+" [ref=e48] [cursor=pointer] + - list [ref=e50]: + - listitem [ref=e51]: return await conn.QueryFirstOrDefaultAsync(@" + - listitem [ref=e52]: + - heading "QuantEngine.Web.Services.AuthService.AuthenticateAsync(string username, string password, string ipAddress) in AuthService.cs" [level=3] [ref=e53]: + - text: QuantEngine.Web.Services.AuthService.AuthenticateAsync(string username, string password, string ipAddress) in + - code [ref=e54]: AuthService.cs + - button "+" [ref=e55] [cursor=pointer] + - list [ref=e57]: + - listitem [ref=e58]: var account = await _workspaceRepository.GetAccountByUsernameAsync(username.Trim()); + - listitem [ref=e59]: + - heading "QuantEngine.Web.Pages.Account.LoginModel.OnPostAsync(string username, string password, bool rememberUsername) in Login.cshtml.cs" [level=3] [ref=e60]: + - text: QuantEngine.Web.Pages.Account.LoginModel.OnPostAsync(string username, string password, bool rememberUsername) in + - code [ref=e61]: Login.cshtml.cs + - button "+" [ref=e62] [cursor=pointer] + - list [ref=e64]: + - listitem [ref=e65]: var account = await _authService.AuthenticateAsync(username, password, ipAddress); + - listitem [ref=e66]: + - heading "Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.ExecutorFactory+GenericTaskHandlerMethod.Convert(object taskAsObject)" [level=3] [ref=e67] + - listitem [ref=e68]: + - heading "Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.ExecutorFactory+GenericTaskHandlerMethod.Execute(object receiver, object[] arguments)" [level=3] [ref=e69] + - listitem [ref=e70]: + - heading "Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.InvokeHandlerMethodAsync()" [level=3] [ref=e71] + - listitem [ref=e72]: + - heading "Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.InvokeNextPageFilterAsync()" [level=3] [ref=e73] + - listitem [ref=e74]: + - heading "Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.Rethrow(PageHandlerExecutedContext context)" [level=3] [ref=e75] + - listitem [ref=e76]: + - heading "Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted)" [level=3] [ref=e77] + - listitem [ref=e78]: + - heading "Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker.InvokeInnerFilterAsync()" [level=3] [ref=e79] + - listitem [ref=e80]: + - heading "Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, object state, bool isCompleted)" [level=3] [ref=e81] + - listitem [ref=e82]: + - heading "Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)" [level=3] [ref=e83] + - listitem [ref=e84]: + - heading "Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted)" [level=3] [ref=e85] + - listitem [ref=e86]: + - heading "Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, object state, bool isCompleted)" [level=3] [ref=e87] + - listitem [ref=e88]: + - heading "Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Logged|17_1(ResourceInvoker invoker)" [level=3] [ref=e89] + - listitem [ref=e90]: + - heading "Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Logged|17_1(ResourceInvoker invoker)" [level=3] [ref=e91] + - listitem [ref=e92]: + - heading "Microsoft.AspNetCore.Routing.EndpointMiddleware.g__AwaitRequestTask|7_0(Endpoint endpoint, Task requestTask, ILogger logger)" [level=3] [ref=e93] + - listitem [ref=e94]: + - heading "Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)" [level=3] [ref=e95] + - listitem [ref=e96]: + - heading "Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)" [level=3] [ref=e97] + - listitem [ref=e98]: + - heading "Serilog.AspNetCore.RequestLoggingMiddleware.Invoke(HttpContext httpContext)" [level=3] [ref=e99] + - listitem [ref=e100]: + - heading "Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)" [level=3] [ref=e101] + - listitem [ref=e102]: + - button "Show raw exception details" [ref=e104] [cursor=pointer] +``` + +# Test source + +```ts + 1 | import { test, expect } from '@playwright/test'; + 2 | import * as fs from 'fs'; + 3 | import * as path from 'path'; + 4 | + 5 | test.describe('QE-M1-02: Collection Run List & Detail Verification', () => { + 6 | // Login before each test + 7 | test.beforeEach(async ({ page }) => { + 8 | await page.goto('/Account/Login'); + 9 | await page.waitForLoadState('domcontentloaded'); + 10 | + 11 | // Fill login form with credentials (admin/admin) + 12 | const usernameInput = page.locator('#username'); + 13 | const passwordInput = page.locator('#password'); + 14 | const loginButton = page.locator('#loginBtn'); + 15 | + 16 | await usernameInput.fill('admin'); + 17 | await passwordInput.fill('admin'); + 18 | await loginButton.click(); + 19 | + 20 | // Wait for login to complete + 21 | await page.waitForLoadState('domcontentloaded'); + 22 | }); + 23 | + 24 | test('QE-M1-02: Collection run renders in list with API-derived expected values', async ({ page }) => { + 25 | // Step 1: Fetch expected values from API (source of truth) + 26 | const apiResponse = await page.request.get('/api/collection/runs?limit=20'); +> 27 | expect(apiResponse.ok()).toBeTruthy(); + | ^ Error: expect(received).toBeTruthy() + 28 | + 29 | const responseJson = await apiResponse.json(); + 30 | const runs = (responseJson as any).runs || []; + 31 | + 32 | // Fail if no collection runs exist in database + 33 | if (runs.length === 0) { + 34 | throw new Error( + 35 | 'No collection runs in DB — run the daily-collection job first. ' + + 36 | 'Expected at least 1 run from kis_collection_runs table.' + 37 | ); + 38 | } + 39 | + 40 | // Extract expected values from most recent run (first in list) + 41 | const expectedRun = runs[0]; + 42 | const expectedRunId = expectedRun.runId; + 43 | const expectedTotalSnapshots = expectedRun.totalSnapshots ?? 0; + 44 | const expectedStatus = expectedRun.status; // e.g., "completed", "running", "failed" + 45 | + 46 | // Map status to Korean text (same logic as Index.cshtml — unknown statuses + 47 | // like COMPLETED_WITH_ERRORS render the raw status string in a secondary badge) + 48 | let expectedStatusText = String(expectedStatus ?? ''); + 49 | if (expectedStatus?.toLowerCase() === 'completed') { + 50 | expectedStatusText = '완료'; + 51 | } else if (expectedStatus?.toLowerCase() === 'running') { + 52 | expectedStatusText = '진행 중'; + 53 | } else if (expectedStatus?.toLowerCase() === 'failed') { + 54 | expectedStatusText = '실패'; + 55 | } + 56 | + 57 | console.log( + 58 | `\n=== QE-M1-02 Test Started ===\n` + + 59 | `Expected RunId: ${expectedRunId}\n` + + 60 | `Expected TotalSnapshots: ${expectedTotalSnapshots}\n` + + 61 | `Expected Status: ${expectedStatus} (rendered as: ${expectedStatusText})\n` + 62 | ); + 63 | + 64 | // Step 2: Navigate to Collection admin page + 65 | await page.goto('/Admin/Collection'); + 66 | await page.waitForLoadState('domcontentloaded'); + 67 | + 68 | // Step 3: Verify page title contains "데이터 수집" (collection) + 69 | const pageTitle = await page.title(); + 70 | expect(pageTitle).toContain('데이터 수집'); + 71 | + 72 | // Step 4: Assert that a row containing the expected runId is visible + 73 | const runIdCell = page.locator(`td:has-text("${expectedRunId}")`); + 74 | await expect(runIdCell).toBeVisible(); + 75 | console.log(`✓ RunId row found and visible: ${expectedRunId}`); + 76 | + 77 | // Step 5: Find the row containing this runId and verify the snapshot count + 78 | const tableRow = runIdCell.locator('xpath=ancestor::tr'); + 79 | + 80 | // Within the row, find all td elements and map to columns + 81 | // Columns: 실행 ID (0), 시작 시간 (1), 종료 시간 (2), 상태 (3), 스냅샷 수 (4), 오류 수 (5) + 82 | const cells = tableRow.locator('td'); + 83 | const cellCount = await cells.count(); + 84 | expect(cellCount).toBeGreaterThanOrEqual(5); // At least 5 columns + 85 | + 86 | // Cell 4 (index 4) is "스냅샷 수" (total snapshots) + 87 | const snapshotCell = cells.nth(4); + 88 | const snapshotText = await snapshotCell.textContent(); + 89 | expect(snapshotText?.trim()).toBe(String(expectedTotalSnapshots)); + 90 | console.log(`✓ Snapshot count matches: ${snapshotText?.trim()} == ${expectedTotalSnapshots}`); + 91 | + 92 | // Cell 3 (index 3) is "상태" (status badge) + 93 | const statusCell = cells.nth(3); + 94 | const statusBadge = statusCell.locator('span.badge'); + 95 | const statusBadgeText = await statusBadge.textContent(); + 96 | expect(statusBadgeText?.trim()).toBe(expectedStatusText); + 97 | console.log(`✓ Status badge matches: ${statusBadgeText?.trim()} == ${expectedStatusText}`); + 98 | + 99 | // Step 6: Create screenshot directory and take screenshot of collection list + 100 | const screenshotDir = path.join(process.cwd(), 'Temp', 'evidence', 'QE-M1-02', 'screenshots'); + 101 | fs.mkdirSync(screenshotDir, { recursive: true }); + 102 | + 103 | await page.screenshot({ + 104 | path: path.join(screenshotDir, '01-collection-page.png'), + 105 | fullPage: true, + 106 | }); + 107 | console.log(`✓ Screenshot saved: 01-collection-page.png`); + 108 | + 109 | // Step 7: Navigate to the run detail page + 110 | // The detail page route is /Admin/Collection/{runId} + 111 | await page.goto(`/Admin/Collection/${expectedRunId}`); + 112 | await page.waitForLoadState('domcontentloaded'); + 113 | + 114 | // Step 8: Verify detail page title contains the runId + 115 | const detailPageTitle = await page.title(); + 116 | expect(detailPageTitle).toContain('수집 실행 상세'); + 117 | + 118 | // Step 9: Verify that the RunId is displayed on the detail page + 119 | // The page title shows: "수집 실행 상세 - {runId}" + 120 | const pageHeading = page.locator('h2.page-title'); + 121 | const headingText = await pageHeading.textContent(); + 122 | expect(headingText).toContain(expectedRunId); + 123 | console.log(`✓ Detail page title contains RunId: ${headingText}`); + 124 | + 125 | // Step 10: Verify snapshots count is displayed on detail page + 126 | // The snapshot count appears in a card with "스냅샷 수" as the title + 127 | const snapshotCountCard = page.locator('h4.card-title:has-text("스냅샷 수")'); +``` \ No newline at end of file diff --git a/test-results/qe-m1-02-collection-run-QE-ae870-API-derived-expected-values-evidence/test-failed-1.png b/test-results/qe-m1-02-collection-run-QE-ae870-API-derived-expected-values-evidence/test-failed-1.png new file mode 100644 index 00000000..b0f2ecbe Binary files /dev/null and b/test-results/qe-m1-02-collection-run-QE-ae870-API-derived-expected-values-evidence/test-failed-1.png differ diff --git a/test-results/qe-m1-02-collection-run-QE-ae870-API-derived-expected-values-evidence/trace.zip b/test-results/qe-m1-02-collection-run-QE-ae870-API-derived-expected-values-evidence/trace.zip new file mode 100644 index 00000000..53c8c9a1 Binary files /dev/null and b/test-results/qe-m1-02-collection-run-QE-ae870-API-derived-expected-values-evidence/trace.zip differ diff --git a/test-results/real-login-result.png b/test-results/real-login-result.png deleted file mode 100644 index c744c9af..00000000 Binary files a/test-results/real-login-result.png and /dev/null differ