diff --git a/login-final-screenshot.png b/login-final-screenshot.png new file mode 100644 index 0000000..da8a76f Binary files /dev/null and b/login-final-screenshot.png differ diff --git a/login-test.mjs b/login-test.mjs new file mode 100644 index 0000000..e7bf5df --- /dev/null +++ b/login-test.mjs @@ -0,0 +1,44 @@ +import { chromium } from '@playwright/test'; + +(async () => { + const browser = await chromium.launch(); + const page = await browser.newPage(); + + try { + await page.goto('http://localhost:5265/login'); + + await page.fill('input[name="username"]', 'admin'); + await page.fill('input[name="password"]', 'admin'); + await page.click('button[type="submit"]'); + + console.log('✓ Login form submitted'); + console.log('✓ Waiting 3 seconds for dashboard redirect...'); + + await page.waitForNavigation({ waitUntil: 'load', timeout: 10000 }); + + const url = page.url(); + const content = await page.content(); + + console.log(`✓ Navigation complete`); + console.log(` URL: ${url}`); + + if (url.includes('/dashboard')) { + if (content.includes('Not Found')) { + console.log('✗ Dashboard URL but Not Found error'); + } else if (content.includes('관리자 대시보드')) { + console.log('✓✓✓ SUCCESS: Dashboard fully loaded!'); + } else { + console.log('✓ Dashboard page loaded (content check)'); + } + } else { + console.log('⚠ Not on dashboard URL'); + } + + await page.screenshot({ path: './login-final-screenshot.png' }); + + } catch (e) { + console.error('Test error:', e.message.substring(0, 70)); + } + + await browser.close(); +})(); diff --git a/run-test.mjs b/run-test.mjs new file mode 100644 index 0000000..c318d7f --- /dev/null +++ b/run-test.mjs @@ -0,0 +1,43 @@ +import { chromium } from '@playwright/test'; + +(async () => { + const browser = await chromium.launch(); + const page = await browser.newPage(); + + try { + await page.goto('http://localhost:5265/login'); + await page.fill('input[name="username"]', 'admin'); + await page.fill('input[name="password"]', 'admin'); + await page.click('button[type="submit"]'); + + console.log('Waiting for dashboard via auth-redirect...'); + + try { + await page.waitForNavigation({ waitUntil: 'load', timeout: 10000 }); + } catch (e) { + // Expected - might timeout if already on dashboard + } + + const url = page.url(); + const content = await page.content(); + + console.log('Final URL: ' + url); + + if (url.includes('/dashboard')) { + if (content.includes('관리자 대시보드')) { + console.log('✓✓✓ SUCCESS: Login complete and dashboard loaded!'); + } else if (content.includes('Not Found')) { + console.log('✗ Not Found error'); + } + } else { + console.log('URL is: ' + url); + } + + await page.screenshot({ path: './test-result.png' }); + + } catch (e) { + console.error('Error:', e.message); + } + + await browser.close(); +})(); diff --git a/src/dotnet/QuantEngine.Web/Client/Pages/Login.razor b/src/dotnet/QuantEngine.Web/Client/Pages/Login.razor deleted file mode 100644 index ef381ed..0000000 --- a/src/dotnet/QuantEngine.Web/Client/Pages/Login.razor +++ /dev/null @@ -1,343 +0,0 @@ -@page "/login" -@layout EmptyLayout -@using Microsoft.AspNetCore.Components.Authorization -@using QuantEngine.Web.Client.Infrastructure -@inject HttpClient Http -@inject NavigationManager Navigation -@inject AuthenticationStateProvider AuthStateProvider - -로그인 - QuantEngine - -
-
- - - @if (!string.IsNullOrEmpty(ErrorMessage)) - { -
- @ErrorMessage -
- } - - @if (IsLoading) - { -
- 로그인 중입니다... -
- } - - - - -
-
- - - -@code { - private string Username = ""; - private string Password = ""; - private bool RememberUsername = false; - private string ErrorMessage = ""; - private bool IsLoading = false; - - protected override void OnInitialized() - { - var savedUsername = Console.Out; - } - - private async Task HandleLoginAsync() - { - if (string.IsNullOrWhiteSpace(Username) || string.IsNullOrWhiteSpace(Password)) - { - ErrorMessage = "아이디와 비밀번호를 입력하세요."; - return; - } - - IsLoading = true; - ErrorMessage = ""; - - try - { - var loginRequest = new { username = Username, password = Password }; - var response = await Http.PostAsJsonAsync("/api/auth/login", loginRequest); - - if (response.IsSuccessStatusCode) - { - using var stream = await response.Content.ReadAsStreamAsync(); - using var reader = new System.IO.StreamReader(stream); - var json = await reader.ReadToEndAsync(); - var result = System.Text.Json.JsonSerializer.Deserialize(json); - - if (result != null && !string.IsNullOrEmpty(result.AccessToken)) - { - // Get auth provider and mark as authenticated - if (AuthStateProvider is CustomAuthenticationStateProvider authProvider) - { - await authProvider.MarkUserAsAuthenticatedAsync( - result.Username ?? Username, - result.AccessToken, - result.Role ?? "Admin", - RememberUsername - ); - } - - // Navigate to dashboard - Navigation.NavigateTo("/dashboard", forceLoad: false); - } - else - { - ErrorMessage = "로그인 응답이 올바르지 않습니다."; - IsLoading = false; - } - } - else - { - ErrorMessage = "로그인 실패: 아이디 또는 비밀번호가 올바르지 않습니다."; - IsLoading = false; - } - } - catch (Exception ex) - { - ErrorMessage = $"오류 발생: {ex.Message}"; - IsLoading = false; - } - } - - private class LoginResponse - { - public bool Success { get; set; } - public string? Username { get; set; } - public string? Role { get; set; } - public string? AccessToken { get; set; } - public string? ExpiresAt { get; set; } - } -} diff --git a/src/dotnet/QuantEngine.Web/Program.cs b/src/dotnet/QuantEngine.Web/Program.cs index 9ddcd78..13c619e 100644 --- a/src/dotnet/QuantEngine.Web/Program.cs +++ b/src/dotnet/QuantEngine.Web/Program.cs @@ -178,13 +178,13 @@ catch (Exception ex) Log.Warning("Hangfire setup failed: {Message}", ex.Message); } -// Root path - redirect unauthenticated to /login (Blazor component) +// Root path - redirect unauthenticated to /login.html (static file) app.MapGet("/", async (HttpContext ctx) => { var isAuthenticated = ctx.User?.Identity?.IsAuthenticated ?? false; if (!isAuthenticated) { - ctx.Response.Redirect("/login"); + ctx.Response.Redirect("/login.html"); } else { @@ -194,6 +194,12 @@ app.MapGet("/", async (HttpContext ctx) => await Task.CompletedTask; }); +// Map /login to static login.html +app.MapGet("/login", (HttpContext ctx) => +{ + ctx.Response.Redirect("/login.html", permanent: false); +}); + // Collection API Endpoints (must be before MapRazorComponents) app.MapCollectionEndpoints(); diff --git a/src/dotnet/QuantEngine.Web/wwwroot/auth-redirect.html b/src/dotnet/QuantEngine.Web/wwwroot/auth-redirect.html new file mode 100644 index 0000000..6245746 --- /dev/null +++ b/src/dotnet/QuantEngine.Web/wwwroot/auth-redirect.html @@ -0,0 +1,62 @@ + + + + + + 로그인 중... + + + +
+
+

로그인 중입니다. 잠시만 기다려주세요...

+
+ + + + diff --git a/src/dotnet/QuantEngine.Web/wwwroot/login.html b/src/dotnet/QuantEngine.Web/wwwroot/login.html index b02f4ed..b143fc7 100644 --- a/src/dotnet/QuantEngine.Web/wwwroot/login.html +++ b/src/dotnet/QuantEngine.Web/wwwroot/login.html @@ -318,13 +318,15 @@ localStorage.setItem('quant_admin_remember_username', 'false'); } - // 대시보드로 이동 + // 로그인 성공 메시지 표시 alertDiv.className = 'alert alert-success show'; - alertDiv.textContent = '로그인 성공! 대시보드로 이동합니다.'; + alertDiv.textContent = '로그인 성공! 대시보드로 이동합니다...'; + // Blazor 앱이 localStorage에서 토큰을 읽고 초기화할 시간을 충분히 준다 + // 4초 대기 후 대시보드로 이동 setTimeout(() => { window.location.href = '/dashboard'; - }, 1000); + }, 4000); } else { const data = await response.json(); alertDiv.className = 'alert alert-error show'; diff --git a/test-result.png b/test-result.png new file mode 100644 index 0000000..2542154 Binary files /dev/null and b/test-result.png differ