chore: deprecate and remove blazor client code and project configurations
Validators (Pushes and Pull Requests) / validate-ui-and-storage (push) Successful in 22s
Prepare Release / Build & Create Release (push) Failing after 58s
Prepare Release / Release Notification (push) Successful in 1s
Validators (Pushes and Pull Requests) / validate-core (push) Has been cancelled
Validators (Pushes and Pull Requests) / validate-ui-and-storage (push) Successful in 22s
Prepare Release / Build & Create Release (push) Failing after 58s
Prepare Release / Release Notification (push) Successful in 1s
Validators (Pushes and Pull Requests) / validate-core (push) Has been cancelled
This commit is contained in:
@@ -1,61 +0,0 @@
|
||||
@namespace QuantEngine.Web.Client.Components
|
||||
@inject IDialogService DialogService
|
||||
|
||||
@code {
|
||||
public static async Task<bool> 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<ConfirmDialog>
|
||||
{
|
||||
{ x => x.Title, title },
|
||||
{ x => x.Message, message },
|
||||
{ x => x.ConfirmText, confirmText },
|
||||
{ x => x.CancelText, cancelText }
|
||||
};
|
||||
|
||||
var dialog = await dialogService.ShowAsync<ConfirmDialog>(title, parameters, options);
|
||||
var result = await dialog.Result;
|
||||
|
||||
return !result.Canceled && (bool?)result.Data == true;
|
||||
}
|
||||
}
|
||||
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
<MudStack Spacing="2">
|
||||
<MudText Typo="Typo.h6">@Title</MudText>
|
||||
<MudText Typo="Typo.body2">@Message</MudText>
|
||||
</MudStack>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="Cancel" Color="Color.Default">@CancelText</MudButton>
|
||||
<MudButton OnClick="Confirm" Color="Color.Primary" Variant="Variant.Filled">@ConfirmText</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
|
||||
@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();
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
@namespace QuantEngine.Web.Client.Components
|
||||
|
||||
<MudStack Spacing="2" Class="form-field">
|
||||
<label class="form-label">
|
||||
@Label
|
||||
@if (Required)
|
||||
{
|
||||
<span class="text-error">*</span>
|
||||
}
|
||||
</label>
|
||||
|
||||
@switch (Type)
|
||||
{
|
||||
case "text":
|
||||
case "email":
|
||||
case "password":
|
||||
case "number":
|
||||
<MudTextField T="string"
|
||||
Value="@Value"
|
||||
ValueChanged="@((string v) => ValueChanged.InvokeAsync(v))"
|
||||
Variant="Variant.Outlined"
|
||||
FullWidth="true"
|
||||
Placeholder="@Placeholder"
|
||||
Type="@Type"
|
||||
Required="@Required"
|
||||
ErrorText="@ErrorMessage" />
|
||||
break;
|
||||
|
||||
case "textarea":
|
||||
<MudTextField T="string"
|
||||
Value="@Value"
|
||||
ValueChanged="@((string v) => ValueChanged.InvokeAsync(v))"
|
||||
Variant="Variant.Outlined"
|
||||
FullWidth="true"
|
||||
Placeholder="@Placeholder"
|
||||
Lines="5"
|
||||
Required="@Required"
|
||||
ErrorText="@ErrorMessage" />
|
||||
break;
|
||||
|
||||
case "select":
|
||||
<MudSelect T="string"
|
||||
Value="@Value"
|
||||
ValueChanged="@((string v) => ValueChanged.InvokeAsync(v))"
|
||||
Variant="Variant.Outlined"
|
||||
FullWidth="true"
|
||||
Required="@Required">
|
||||
@foreach (var option in Options)
|
||||
{
|
||||
<MudSelectItem T="string" Value="@option">@option</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
break;
|
||||
|
||||
case "checkbox":
|
||||
<MudCheckBox T="bool"
|
||||
Checked="@(Value == "true")"
|
||||
CheckedChanged="@((bool v) => ValueChanged.InvokeAsync(v ? "true" : "false"))">
|
||||
@Label
|
||||
</MudCheckBox>
|
||||
break;
|
||||
|
||||
case "date":
|
||||
<MudTextField T="string"
|
||||
Value="@Value"
|
||||
ValueChanged="@((string v) => ValueChanged.InvokeAsync(v))"
|
||||
Variant="Variant.Outlined"
|
||||
FullWidth="true"
|
||||
Type="date"
|
||||
Required="@Required" />
|
||||
break;
|
||||
}
|
||||
|
||||
@if (!string.IsNullOrEmpty(HelpText))
|
||||
{
|
||||
<MudText Typo="Typo.caption" Class="text-muted">@HelpText</MudText>
|
||||
}
|
||||
</MudStack>
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public string Label { get; set; } = "";
|
||||
|
||||
[Parameter]
|
||||
public string Type { get; set; } = "text";
|
||||
|
||||
[Parameter]
|
||||
public string Value { get; set; } = "";
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<string> 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<string> Options { get; set; } = new();
|
||||
}
|
||||
|
||||
<style>
|
||||
.form-field {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.form-label {
|
||||
display: block;
|
||||
font-weight: 500;
|
||||
font-size: 0.875rem;
|
||||
color: var(--mud-palette-text-primary);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.form-label .text-error {
|
||||
color: var(--mud-palette-error);
|
||||
}
|
||||
</style>
|
||||
-249
@@ -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<AuthenticationState> 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<string>("localStorage.getItem", TokenKey);
|
||||
string username = await _jsRuntime.InvokeAsync<string>("localStorage.getItem", UsernameKey);
|
||||
string role = await _jsRuntime.InvokeAsync<string>("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<bool>(RememberUsernameKey);
|
||||
if (!rememberUsername)
|
||||
{
|
||||
await _localStorage.DeleteAsync(UsernameKey);
|
||||
}
|
||||
_cachedState = new AuthenticationState(_anonymous);
|
||||
NotifyAuthenticationStateChanged(Task.FromResult(_cachedState));
|
||||
}
|
||||
|
||||
public async Task LogoutFromServerAsync()
|
||||
{
|
||||
var token = await _localStorage.GetAsync<string>(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<string?> GetRememberedUsernameAsync()
|
||||
{
|
||||
var rememberUsername = await _localStorage.GetAsync<bool>(RememberUsernameKey);
|
||||
if (!rememberUsername)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return await _localStorage.GetAsync<string>(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
@inherits LayoutComponentBase
|
||||
@rendermode InteractiveWebAssembly
|
||||
|
||||
<style>
|
||||
:global(body) {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
}
|
||||
|
||||
:global(html, body, #app) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
@Body
|
||||
|
||||
@code {
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
@inherits LayoutComponentBase
|
||||
|
||||
@Body
|
||||
|
||||
<style>
|
||||
:global(html, body) {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
:global(#app) {
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
}
|
||||
</style>
|
||||
@@ -1,154 +0,0 @@
|
||||
@inherits LayoutComponentBase
|
||||
@using QuantEngine.Web.Client.Theme
|
||||
@inject HttpClient Http
|
||||
@inject AuthenticationStateProvider AuthStateProvider
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
<!-- ✅ MudBlazor Providers (Required for Interactive WebAssembly) -->
|
||||
<MudThemeProvider Theme="@_theme" />
|
||||
<MudPopoverProvider />
|
||||
<MudDialogProvider />
|
||||
<MudSnackbarProvider />
|
||||
|
||||
<MudLayout>
|
||||
<!-- Top Navigation Bar -->
|
||||
<MudAppBar Elevation="1" Dense="false" Color="Color.Surface" Class="mud-appbar-dense">
|
||||
<MudHidden Breakpoint="Breakpoint.SmAndUp" Invert="true">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Menu" Color="Color.Inherit" Edge="Edge.Start" OnClick="@(() => navOpen = !navOpen)" />
|
||||
</MudHidden>
|
||||
|
||||
<MudText Typo="Typo.h6" Class="ml-2">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Dashboard" Class="me-2" />
|
||||
QuantEngine
|
||||
</MudText>
|
||||
|
||||
<MudSpacer />
|
||||
|
||||
<!-- User Menu -->
|
||||
<AuthorizeView Context="authContext">
|
||||
<Authorized>
|
||||
<MudMenu AnchorOrigin="Origin.BottomRight" TransformOrigin="Origin.TopRight" Class="ml-2">
|
||||
<ActivatorContent>
|
||||
<MudAvatar Color="Color.Primary" Image="@GetUserInitials()" Class="cursor-pointer">
|
||||
@GetFirstLetter(authContext.User.Identity?.Name)
|
||||
</MudAvatar>
|
||||
</ActivatorContent>
|
||||
<ChildContent>
|
||||
<MudMenuItem>
|
||||
<MudText Typo="Typo.body2">
|
||||
<strong>@authContext.User.Identity?.Name</strong>
|
||||
</MudText>
|
||||
</MudMenuItem>
|
||||
<MudDivider />
|
||||
<MudMenuItem href="/profile">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Person" Class="mr-2" Size="Size.Small" />
|
||||
프로필
|
||||
</MudMenuItem>
|
||||
<MudMenuItem href="/settings">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Settings" Class="mr-2" Size="Size.Small" />
|
||||
설정
|
||||
</MudMenuItem>
|
||||
<MudDivider />
|
||||
<MudMenuItem OnClick="HandleLogoutAsync">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Logout" Class="mr-2" Size="Size.Small" Color="Color.Error" />
|
||||
<MudText Color="Color.Error">로그아웃</MudText>
|
||||
</MudMenuItem>
|
||||
</ChildContent>
|
||||
</MudMenu>
|
||||
</Authorized>
|
||||
</AuthorizeView>
|
||||
</MudAppBar>
|
||||
|
||||
<!-- Sidebar Navigation -->
|
||||
<MudDrawer Open="@navOpen" Variant="DrawerVariant.Responsive" Elevation="1" FixedOpen="@fixedOpen">
|
||||
<MudDrawerHeader Class="d-flex align-center justify-space-between">
|
||||
<MudText Typo="Typo.h6" Class="px-2">메뉴</MudText>
|
||||
<MudHidden Breakpoint="Breakpoint.Md" Invert="true">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.ChevronLeft"
|
||||
OnClick="ToggleDrawer"
|
||||
Class="mx-1" />
|
||||
</MudHidden>
|
||||
</MudDrawerHeader>
|
||||
|
||||
<MudNavMenu>
|
||||
<NavMenu />
|
||||
</MudNavMenu>
|
||||
|
||||
<!-- Drawer Footer -->
|
||||
<div class="mud-drawer-footer">
|
||||
<MudDivider />
|
||||
<div style="padding: 16px;">
|
||||
<MudText Typo="Typo.caption">
|
||||
<strong>QuantEngine</strong>
|
||||
</MudText>
|
||||
<MudText Typo="Typo.caption">
|
||||
v@appVersion
|
||||
</MudText>
|
||||
<MudText Typo="Typo.caption" Class="mt-2">
|
||||
배포: @buildTime
|
||||
</MudText>
|
||||
</div>
|
||||
</div>
|
||||
</MudDrawer>
|
||||
|
||||
<!-- Main Content Area -->
|
||||
<MudMainContent Class="mud-main-content-enhanced">
|
||||
<MudContainer MaxWidth="MaxWidth.False" Class="pa-6">
|
||||
@Body
|
||||
</MudContainer>
|
||||
</MudMainContent>
|
||||
</MudLayout>
|
||||
|
||||
@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<VersionInfo>("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; }
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
<MudNavMenu>
|
||||
<!-- Main Navigation -->
|
||||
<MudNavLink Href="/dashboard" Icon="@Icons.Material.Filled.Dashboard" Match="NavLinkMatch.All">
|
||||
대시보드
|
||||
</MudNavLink>
|
||||
|
||||
<!-- Admin Section -->
|
||||
<MudNavGroup Title="관리" Icon="@Icons.Material.Filled.AdminPanelSettings" Expanded="true">
|
||||
<MudNavLink Href="/users" Icon="@Icons.Material.Filled.People">사용자 관리</MudNavLink>
|
||||
<MudNavLink Href="/collection" Icon="@Icons.Material.Filled.CloudDownload">데이터 수집</MudNavLink>
|
||||
<MudNavLink Href="/monitoring" Icon="@Icons.Material.Filled.Timeline">수집 모니터링</MudNavLink>
|
||||
</MudNavGroup>
|
||||
|
||||
<!-- Operations -->
|
||||
<MudNavLink Href="/operations" Icon="@Icons.Material.Filled.PlaylistPlay" Match="NavLinkMatch.Prefix">
|
||||
운영 리포트
|
||||
</MudNavLink>
|
||||
</MudNavMenu>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
@page "/collection"
|
||||
@attribute [Authorize]
|
||||
@using QuantEngine.Web.Client.Services
|
||||
@inject ApiClient ApiClient
|
||||
@inject ILogger<Collection> Logger
|
||||
|
||||
<PageTitle>QuantEngine - Collection</PageTitle>
|
||||
|
||||
<MudText Typo="Typo.h4" Class="mb-2">Data Collection</MudText>
|
||||
<MudText Typo="Typo.body2" Class="mb-4">KIS API data collection dashboard. API-first로만 동작합니다.</MudText>
|
||||
|
||||
<MudStack Row="true" Spacing="2" Class="mb-4">
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="@StartCollectionAsync" Disabled="@IsProcessing">
|
||||
@(IsProcessing ? "Running..." : "Start Collection")
|
||||
</MudButton>
|
||||
<MudButton Variant="Variant.Outlined" OnClick="@RefreshAsync" Disabled="@IsProcessing">Refresh</MudButton>
|
||||
</MudStack>
|
||||
|
||||
@if (IsLoading)
|
||||
{
|
||||
<MudProgressLinear Indeterminate="true" Color="Color.Primary" Class="mb-4" />
|
||||
}
|
||||
else if (DashboardState != null)
|
||||
{
|
||||
<MudGrid Spacing="2" Class="mb-4">
|
||||
<MudItem xs="12" sm="4">
|
||||
<MudPaper Class="pa-4" Elevation="2">
|
||||
<MudText Typo="Typo.caption">Last Run</MudText>
|
||||
<MudText Typo="Typo.h6">@(DashboardState.LastRunStatus ?? "N/A")</MudText>
|
||||
<MudText Typo="Typo.body2">@(DashboardState.LastFinishedAt ?? "Not finished")</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="4">
|
||||
<MudPaper Class="pa-4" Elevation="2">
|
||||
<MudText Typo="Typo.caption">Total Snapshots</MudText>
|
||||
<MudText Typo="Typo.h6">@DashboardState.TotalSnapshots</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="4">
|
||||
<MudPaper Class="pa-4" Elevation="2">
|
||||
<MudText Typo="Typo.caption">Total Errors</MudText>
|
||||
<MudText Typo="Typo.h6">@DashboardState.TotalErrors</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
|
||||
@if (DashboardState.RecentErrors.Count > 0)
|
||||
{
|
||||
<MudPaper Class="pa-4 mb-4" Elevation="2">
|
||||
<MudText Typo="Typo.h6" Class="mb-3">Recent Errors</MudText>
|
||||
<MudTable Items="@DashboardState.RecentErrors" Dense="true" Hover="true">
|
||||
<HeaderContent>
|
||||
<MudTh>Source</MudTh>
|
||||
<MudTh>Kind</MudTh>
|
||||
<MudTh>Ticker</MudTh>
|
||||
<MudTh>Message</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd DataLabel="Source">@context.SourceName</MudTd>
|
||||
<MudTd DataLabel="Kind">@context.ErrorKind</MudTd>
|
||||
<MudTd DataLabel="Ticker">@context.Ticker</MudTd>
|
||||
<MudTd DataLabel="Message">@context.ErrorMessage</MudTd>
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
</MudPaper>
|
||||
}
|
||||
|
||||
@if (RecentRuns != null && RecentRuns.Count > 0)
|
||||
{
|
||||
<MudPaper Class="pa-4" Elevation="2">
|
||||
<MudText Typo="Typo.h6" Class="mb-3">Recent Runs</MudText>
|
||||
<MudTable Items="@RecentRuns" Dense="true" Hover="true">
|
||||
<HeaderContent>
|
||||
<MudTh>Run ID</MudTh>
|
||||
<MudTh>Status</MudTh>
|
||||
<MudTh>Started</MudTh>
|
||||
<MudTh>Finished</MudTh>
|
||||
<MudTh>Snapshots</MudTh>
|
||||
<MudTh>Errors</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd DataLabel="Run ID" Style="font-family: monospace; font-size: 12px;">@context.RunId</MudTd>
|
||||
<MudTd DataLabel="Status">@context.Status</MudTd>
|
||||
<MudTd DataLabel="Started">@context.StartedAt</MudTd>
|
||||
<MudTd DataLabel="Finished">@context.FinishedAt</MudTd>
|
||||
<MudTd DataLabel="Snapshots">@context.TotalSnapshots</MudTd>
|
||||
<MudTd DataLabel="Errors">@context.TotalErrors</MudTd>
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
</MudPaper>
|
||||
}
|
||||
}
|
||||
|
||||
@code {
|
||||
private CollectionDashboardStateDto? DashboardState;
|
||||
private List<CollectionRunDto>? 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();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
<PageTitle>QuantEngine - Admin Dashboard</PageTitle>
|
||||
|
||||
|
||||
|
||||
<!-- Page Header -->
|
||||
<div class="mb-6">
|
||||
<MudText Typo="Typo.h4" Class="mb-2">관리자 대시보드</MudText>
|
||||
<MudText Typo="Typo.body1" Class="text-muted">시스템 현황 및 데이터 수집 모니터링</MudText>
|
||||
</div>
|
||||
|
||||
<!-- KPI Cards -->
|
||||
<MudGrid Spacing="3" Class="mb-6">
|
||||
<!-- Total Runs -->
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<MudPaper Class="pa-4 mud-card-kpi" Elevation="0" Style="border: 1px solid var(--mud-palette-divider);">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div>
|
||||
<MudText Typo="Typo.caption" Class="text-muted mb-1">총 수집 실행</MudText>
|
||||
<MudText Typo="Typo.h5" Class="text-primary">@TotalRuns</MudText>
|
||||
<MudText Typo="Typo.body2" Class="text-muted mt-2">
|
||||
<MudIcon Icon="@Icons.Material.Filled.TrendingUp" Size="Size.Small" Style="color: #4caf50;" />
|
||||
이번 주 +@WeeklyRuns
|
||||
</MudText>
|
||||
</div>
|
||||
<MudIcon Icon="@Icons.Material.Filled.PlayCircleOutline" Size="Size.Large" Class="text-primary" Style="opacity: 0.3;" />
|
||||
</div>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
|
||||
<!-- Success Rate -->
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<MudPaper Class="pa-4 mud-card-kpi" Elevation="0" Style="border: 1px solid var(--mud-palette-divider);">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div>
|
||||
<MudText Typo="Typo.caption" Class="text-muted mb-1">성공률</MudText>
|
||||
<MudText Typo="Typo.h5" Class="text-success">@SuccessRate%</MudText>
|
||||
<MudText Typo="Typo.body2" Class="text-muted mt-2">
|
||||
<MudIcon Icon="@Icons.Material.Filled.CheckCircle" Size="Size.Small" Style="color: #4caf50;" />
|
||||
최근 30일
|
||||
</MudText>
|
||||
</div>
|
||||
<MudIcon Icon="@Icons.Material.Filled.Assessment" Size="Size.Large" Class="text-success" Style="opacity: 0.3;" />
|
||||
</div>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
|
||||
<!-- Recent Errors -->
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<MudPaper Class="pa-4 mud-card-kpi" Elevation="0" Style="border: 1px solid var(--mud-palette-divider);">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div>
|
||||
<MudText Typo="Typo.caption" Class="text-muted mb-1">최근 에러</MudText>
|
||||
<MudText Typo="Typo.h5" Class="text-error">@RecentErrors</MudText>
|
||||
<MudText Typo="Typo.body2" Class="text-muted mt-2">
|
||||
<MudIcon Icon="@Icons.Material.Filled.ErrorOutline" Size="Size.Small" Style="color: #f44336;" />
|
||||
지난 7일
|
||||
</MudText>
|
||||
</div>
|
||||
<MudIcon Icon="@Icons.Material.Filled.WarningAmber" Size="Size.Large" Class="text-error" Style="opacity: 0.3;" />
|
||||
</div>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
|
||||
<!-- Last Sync -->
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<MudPaper Class="pa-4 mud-card-kpi" Elevation="0" Style="border: 1px solid var(--mud-palette-divider);">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div>
|
||||
<MudText Typo="Typo.caption" Class="text-muted mb-1">마지막 동기화</MudText>
|
||||
<MudText Typo="Typo.h5">@LastSyncTime</MudText>
|
||||
<MudText Typo="Typo.body2" Class="text-muted mt-2">
|
||||
<MudChip T="string" Label="true" Size="Size.Small"
|
||||
Color="@(IsLastSyncSuccess ? Color.Success : Color.Warning)"
|
||||
Variant="Variant.Filled">
|
||||
@(IsLastSyncSuccess ? "성공" : "경고")
|
||||
</MudChip>
|
||||
</MudText>
|
||||
</div>
|
||||
<MudIcon Icon="@Icons.Material.Filled.Schedule" Size="Size.Large" Class="text-secondary" Style="opacity: 0.3;" />
|
||||
</div>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
|
||||
<!-- Main Content Grid -->
|
||||
<MudGrid Spacing="3" Class="mb-6">
|
||||
<!-- Recent Activity Feed -->
|
||||
<MudItem xs="12" md="8">
|
||||
<MudPaper Class="pa-4" Elevation="1">
|
||||
<MudText Typo="Typo.h6" Class="mb-4">최근 활동</MudText>
|
||||
|
||||
@if (RecentActivities.Count == 0)
|
||||
{
|
||||
<MudAlert Severity="Severity.Info">활동 기록이 없습니다.</MudAlert>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudStack Spacing="2">
|
||||
@foreach (var activity in RecentActivities)
|
||||
{
|
||||
<div class="d-flex gap-3 pa-2" style="border-left: 3px solid @GetActivityColor(activity.Type); padding-left: 12px;">
|
||||
<MudIcon Icon="@GetActivityIcon(activity.Type)" Size="Size.Medium" Color="@GetActivityColorEnum(activity.Type)" />
|
||||
<div style="flex: 1;">
|
||||
<MudText Typo="Typo.body2" Class="font-weight-500">@activity.Title</MudText>
|
||||
<MudText Typo="Typo.caption" Class="text-muted">@activity.Timestamp.ToString("yyyy-MM-dd HH:mm:ss")</MudText>
|
||||
<MudText Typo="Typo.body2" Class="mt-1">@activity.Description</MudText>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</MudStack>
|
||||
}
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
|
||||
<!-- System Status -->
|
||||
<MudItem xs="12" md="4">
|
||||
<MudPaper Class="pa-4" Elevation="1">
|
||||
<MudText Typo="Typo.h6" Class="mb-4">시스템 상태</MudText>
|
||||
|
||||
<MudStack Spacing="2">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<MudText Typo="Typo.body2">API 서버</MudText>
|
||||
<MudChip T="string" Label="true" Size="Size.Small" Color="Color.Success" Variant="Variant.Filled">온라인</MudChip>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<MudText Typo="Typo.body2">데이터베이스</MudText>
|
||||
<MudChip T="string" Label="true" Size="Size.Small" Color="Color.Success" Variant="Variant.Filled">연결됨</MudChip>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<MudText Typo="Typo.body2">KIS API</MudText>
|
||||
<MudChip T="string" Label="true" Size="Size.Small" Color="@(KisApiStatus ? Color.Success : Color.Warning)" Variant="Variant.Filled">
|
||||
@(KisApiStatus ? "활성" : "비활성")
|
||||
</MudChip>
|
||||
</div>
|
||||
<MudDivider Class="my-2" />
|
||||
<MudText Typo="Typo.caption" Class="text-muted">마지막 점검: @SystemCheckTime</MudText>
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
|
||||
<!-- Collections Table -->
|
||||
<MudPaper Class="pa-4" Elevation="1">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<MudText Typo="Typo.h6">최근 데이터 수집 실행</MudText>
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" Size="Size.Small" OnClick="RefreshData">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Refresh" Size="Size.Small" Class="mr-2" />
|
||||
새로고침
|
||||
</MudButton>
|
||||
</div>
|
||||
|
||||
@if (Sections.Count == 0)
|
||||
{
|
||||
<MudAlert Severity="Severity.Info">데이터 수집 기록이 없습니다.</MudAlert>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudTable Items="@Sections" Dense="true" Hover="true" Striped="true">
|
||||
<HeaderContent>
|
||||
<MudTh>이름</MudTh>
|
||||
<MudTh>상태</MudTh>
|
||||
<MudTh>시작 시간</MudTh>
|
||||
<MudTh>작업</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd DataLabel="Name">
|
||||
<MudText Typo="Typo.body2">@context.Name</MudText>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Status">
|
||||
<MudChip T="string" Label="true" Size="Size.Small" Color="Color.Primary" Variant="Variant.Filled">
|
||||
@context.Title
|
||||
</MudChip>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Timestamp">
|
||||
<MudText Typo="Typo.body2">@context.Preview</MudText>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Actions">
|
||||
<MudButton Variant="Variant.Text" Size="Size.Small" Color="Color.Primary">상세</MudButton>
|
||||
</MudTd>
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
}
|
||||
</MudPaper>
|
||||
|
||||
<style>
|
||||
.mud-card-kpi {
|
||||
border-radius: 8px !important;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.mud-card-kpi:hover {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1) !important;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.text-primary {
|
||||
color: var(--mud-palette-primary) !important;
|
||||
}
|
||||
|
||||
.text-success {
|
||||
color: var(--mud-palette-success) !important;
|
||||
}
|
||||
|
||||
.text-error {
|
||||
color: var(--mud-palette-error) !important;
|
||||
}
|
||||
|
||||
.text-muted {
|
||||
color: var(--mud-palette-text-secondary) !important;
|
||||
}
|
||||
|
||||
.font-weight-500 {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.gap-3 {
|
||||
gap: 1rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
@code {
|
||||
private readonly List<OperationalReportSection> Sections = new();
|
||||
private readonly List<ActivityLog> 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<OperationalReportData>("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; }
|
||||
}
|
||||
}
|
||||
@@ -1,229 +0,0 @@
|
||||
@page "/monitoring"
|
||||
@attribute [Authorize]
|
||||
@inject HttpClient Http
|
||||
@inject ISnackbar Snackbar
|
||||
|
||||
<PageTitle>QuantEngine - 데이터 수집 모니터링</PageTitle>
|
||||
|
||||
<!-- Page Header -->
|
||||
<div class="mb-6">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<MudText Typo="Typo.h4" Class="mb-2">데이터 수집 모니터링</MudText>
|
||||
<MudText Typo="Typo.body1" Class="text-muted">실시간 수집 작업 상태 및 에러 추적</MudText>
|
||||
</div>
|
||||
<MudButton Variant="Variant.Outlined" Color="Color.Primary" Size="Size.Small"
|
||||
OnClick="RefreshAsync" Disabled="_loading">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Refresh" Size="Size.Small" Class="mr-2" />
|
||||
새로고침
|
||||
</MudButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (_loading)
|
||||
{
|
||||
<MudProgressLinear Indeterminate="true" Color="Color.Primary" Class="mb-4" />
|
||||
}
|
||||
|
||||
<!-- Collection Status Cards -->
|
||||
<MudGrid Spacing="3" Class="mb-6">
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<MudPaper Class="pa-4" Elevation="0" Style="border: 1px solid var(--mud-palette-divider);">
|
||||
<MudText Typo="Typo.caption" Class="text-muted mb-2">진행 중인 작업</MudText>
|
||||
<MudText Typo="Typo.h5">@_runningCount</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<MudPaper Class="pa-4" Elevation="0" Style="border: 1px solid var(--mud-palette-divider);">
|
||||
<MudText Typo="Typo.caption" Class="text-muted mb-2">완료</MudText>
|
||||
<MudText Typo="Typo.h5" Style="color: var(--mud-palette-success);">@_completedCount</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<MudPaper Class="pa-4" Elevation="0" Style="border: 1px solid var(--mud-palette-divider);">
|
||||
<MudText Typo="Typo.caption" Class="text-muted mb-2">실패</MudText>
|
||||
<MudText Typo="Typo.h5" Style="color: var(--mud-palette-error);">@_failedCount</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<MudPaper Class="pa-4" Elevation="0" Style="border: 1px solid var(--mud-palette-divider);">
|
||||
<MudText Typo="Typo.caption" Class="text-muted mb-2">총 스냅샷</MudText>
|
||||
<MudText Typo="Typo.h5">@_totalSnapshots</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
|
||||
<!-- Tabs -->
|
||||
<MudTabs Outlined="true" Class="mb-6">
|
||||
<!-- Recent Runs -->
|
||||
<MudTabPanel Text="최근 실행">
|
||||
<div class="py-4">
|
||||
<MudPaper Class="pa-4" Elevation="1">
|
||||
@if (_recentRuns.Count == 0 && !_loading)
|
||||
{
|
||||
<MudAlert Severity="Severity.Info">최근 실행 기록이 없습니다.</MudAlert>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudTable Items="@_recentRuns" Dense="true" Hover="true" Striped="true">
|
||||
<HeaderContent>
|
||||
<MudTh>실행 ID</MudTh>
|
||||
<MudTh>시작 시간</MudTh>
|
||||
<MudTh>종료 시간</MudTh>
|
||||
<MudTh>상태</MudTh>
|
||||
<MudTh>스냅샷</MudTh>
|
||||
<MudTh>에러</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd DataLabel="Run ID">
|
||||
<MudText Typo="Typo.body2" Class="font-monospace">@context.RunId</MudText>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Start">
|
||||
<MudText Typo="Typo.body2">@FormatTime(context.StartedAt)</MudText>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="End">
|
||||
<MudText Typo="Typo.body2">@(string.IsNullOrEmpty(context.FinishedAt) ? "-" : FormatTime(context.FinishedAt))</MudText>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Status">
|
||||
<MudChip T="string" Label="true" Size="Size.Small"
|
||||
Color="@GetStatusColor(context.Status)"
|
||||
Variant="Variant.Filled">
|
||||
@context.Status
|
||||
</MudChip>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Snapshots">
|
||||
<MudText Typo="Typo.body2">@(context.TotalSnapshots?.ToString() ?? "-")</MudText>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Errors">
|
||||
@if (context.TotalErrors > 0)
|
||||
{
|
||||
<MudChip T="string" Label="true" Size="Size.Small" Color="Color.Error" Variant="Variant.Outlined">
|
||||
@context.TotalErrors
|
||||
</MudChip>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudText Typo="Typo.body2">-</MudText>
|
||||
}
|
||||
</MudTd>
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
}
|
||||
</MudPaper>
|
||||
</div>
|
||||
</MudTabPanel>
|
||||
|
||||
<!-- Error Logs -->
|
||||
<MudTabPanel Text="에러 로그">
|
||||
<div class="py-4">
|
||||
<MudPaper Class="pa-4" Elevation="1">
|
||||
@if (_errors.Count == 0 && !_loading)
|
||||
{
|
||||
<MudAlert Severity="Severity.Success">에러가 없습니다.</MudAlert>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudStack Spacing="2">
|
||||
@foreach (var error in _errors)
|
||||
{
|
||||
<div class="pa-3" style="border-left: 3px solid #f44336; background-color: var(--mud-palette-surface);">
|
||||
<div class="d-flex justify-content-between align-items-start mb-2">
|
||||
<MudText Typo="Typo.body2" Class="font-weight-500">[@error.ErrorKind] @error.ErrorMessage</MudText>
|
||||
<MudText Typo="Typo.caption" Class="text-muted">@FormatTime(error.CreatedAt)</MudText>
|
||||
</div>
|
||||
<MudText Typo="Typo.caption" Class="text-muted">Run: @error.RunId</MudText>
|
||||
@if (!string.IsNullOrEmpty(error.Ticker))
|
||||
{
|
||||
<MudText Typo="Typo.caption" Class="text-muted ml-3">Ticker: @error.Ticker</MudText>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</MudStack>
|
||||
}
|
||||
</MudPaper>
|
||||
</div>
|
||||
</MudTabPanel>
|
||||
</MudTabs>
|
||||
|
||||
@code {
|
||||
private bool _loading = false;
|
||||
private int _runningCount;
|
||||
private int _completedCount;
|
||||
private int _failedCount;
|
||||
private int _totalSnapshots;
|
||||
|
||||
private List<CollectionRunDto> _recentRuns = new();
|
||||
private List<CollectionErrorDto> _errors = new();
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await RefreshAsync();
|
||||
}
|
||||
|
||||
private async Task RefreshAsync()
|
||||
{
|
||||
_loading = true;
|
||||
StateHasChanged();
|
||||
|
||||
try
|
||||
{
|
||||
// 최근 실행 목록 로드
|
||||
var runsResponse = await Http.GetFromJsonAsync<CollectionRunsResponse>("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<CollectionDashboardStateDto>("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<CollectionRunDto> 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<CollectionErrorDto> RecentErrors);
|
||||
private record CollectionErrorDto(
|
||||
string RunId, string SourceName, string ErrorKind,
|
||||
string ErrorMessage, string? Ticker, string CreatedAt);
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
@page "/not-found"
|
||||
@layout MainLayout
|
||||
|
||||
<!-- 🎯 DEBUG MARKER: NOTFOUND_RENDERING -->
|
||||
<div id="notfound-debug-marker" style="display:none;">NOTFOUND_RENDERING_ACTIVE</div>
|
||||
|
||||
<h3>Not Found</h3>
|
||||
<p>Sorry, the content you are looking for does not exist.</p>
|
||||
@@ -1,121 +0,0 @@
|
||||
@page "/operations"
|
||||
@attribute [Authorize]
|
||||
@using QuantEngine.Core.Infrastructure
|
||||
@inject HttpClient Http
|
||||
|
||||
<PageTitle>QuantEngine - Operations</PageTitle>
|
||||
|
||||
<MudText Typo="Typo.h4" Class="mb-2">Operational Report</MudText>
|
||||
<MudText Typo="Typo.body2" Class="mb-4">Temp/operational_report.json만 읽는 운영 고정 화면입니다.</MudText>
|
||||
|
||||
<MudGrid Spacing="2" Class="mb-4">
|
||||
<MudItem xs="12" sm="3">
|
||||
<MudPaper Class="pa-4" Elevation="2">
|
||||
<MudText Typo="Typo.caption">Schema</MudText>
|
||||
<MudText Typo="Typo.h6">@SchemaVersion</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="3">
|
||||
<MudPaper Class="pa-4" Elevation="2">
|
||||
<MudText Typo="Typo.caption">Sections</MudText>
|
||||
<MudText Typo="Typo.h6">@SectionCountLabel</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="3">
|
||||
<MudPaper Class="pa-4" Elevation="2">
|
||||
<MudText Typo="Typo.caption">Source</MudText>
|
||||
<MudText Typo="Typo.h6">@SourceJson</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="3">
|
||||
<MudPaper Class="pa-4" Elevation="2">
|
||||
<MudText Typo="Typo.caption">Generated</MudText>
|
||||
<MudText Typo="Typo.h6">@GeneratedAt</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
|
||||
<MudGrid Spacing="2" Class="mb-4">
|
||||
@foreach (var section in HighlightSections)
|
||||
{
|
||||
<MudItem xs="12" sm="6" md="3" @key="section.Name">
|
||||
<MudPaper Class="pa-4" Elevation="2">
|
||||
<MudText Typo="Typo.caption">@(section.Name)</MudText>
|
||||
<MudText Typo="Typo.h6">@(section.Title)</MudText>
|
||||
<MudText Typo="Typo.body2">@(section.Preview)</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
}
|
||||
</MudGrid>
|
||||
|
||||
<MudPaper Class="pa-4 mb-4" Elevation="2">
|
||||
<MudText Typo="Typo.h6" Class="mb-3">Report Health</MudText>
|
||||
<MudStack Spacing="1">
|
||||
<MudText Typo="Typo.body2">Status: <MudChip T="string" Color="@(HealthLabel == "PASS" ? Color.Success : Color.Warning)" Variant="Variant.Filled">@HealthLabel</MudChip></MudText>
|
||||
<MudText Typo="Typo.body2">Path: @ReportPath</MudText>
|
||||
<MudText Typo="Typo.body2">Sections rendered: @RenderedSectionCountLabel</MudText>
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
|
||||
<MudPaper Class="pa-4" Elevation="2">
|
||||
<MudText Typo="Typo.h6" Class="mb-3">Sections</MudText>
|
||||
@if (Sections.Count == 0)
|
||||
{
|
||||
<MudAlert Severity="Severity.Warning">DATA_MISSING: operational_report.json에 표시할 섹션이 없습니다.</MudAlert>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudTable Items="@Sections" Dense="true" Hover="true">
|
||||
<HeaderContent>
|
||||
<MudTh>Name</MudTh>
|
||||
<MudTh>Title</MudTh>
|
||||
<MudTh>Preview</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd DataLabel="Name">@context.Name</MudTd>
|
||||
<MudTd DataLabel="Title">@context.Title</MudTd>
|
||||
<MudTd DataLabel="Preview">@context.Preview</MudTd>
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
}
|
||||
</MudPaper>
|
||||
|
||||
@code {
|
||||
private readonly List<OperationalReportSection> Sections = new();
|
||||
private readonly List<OperationalReportSection> 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<OperationalReportData>("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";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,238 +0,0 @@
|
||||
@page "/portfolio"
|
||||
@attribute [Authorize]
|
||||
@inject HttpClient Http
|
||||
|
||||
<PageTitle>QuantEngine - 포트폴리오</PageTitle>
|
||||
|
||||
<!-- Page Header -->
|
||||
<div class="mb-6">
|
||||
<MudText Typo="Typo.h4" Class="mb-2">포트폴리오</MudText>
|
||||
<MudText Typo="Typo.body1" Class="text-muted">자산 구성 및 성과 분석</MudText>
|
||||
</div>
|
||||
|
||||
<!-- Summary Cards -->
|
||||
<MudGrid Spacing="3" Class="mb-6">
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<MudPaper Class="pa-4" Elevation="0" Style="border: 1px solid var(--mud-palette-divider);">
|
||||
<MudText Typo="Typo.caption" Class="text-muted mb-1">총 평가액</MudText>
|
||||
<MudText Typo="Typo.h5" Class="text-primary">₩125.5M</MudText>
|
||||
<MudText Typo="Typo.body2" Class="text-success mt-1">+3.2% (이번 달)</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<MudPaper Class="pa-4" Elevation="0" Style="border: 1px solid var(--mud-palette-divider);">
|
||||
<MudText Typo="Typo.caption" Class="text-muted mb-1">보유 종목</MudText>
|
||||
<MudText Typo="Typo.h5">12개</MudText>
|
||||
<MudText Typo="Typo.body2" Class="text-muted mt-1">주식 및 펀드</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<MudPaper Class="pa-4" Elevation="0" Style="border: 1px solid var(--mud-palette-divider);">
|
||||
<MudText Typo="Typo.caption" Class="text-muted mb-1">수익률</MudText>
|
||||
<MudText Typo="Typo.h5" Class="text-success">+8.5%</MudText>
|
||||
<MudText Typo="Typo.body2" Class="text-muted mt-1">연간 기준</MudText>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12" sm="6" md="3">
|
||||
<MudPaper Class="pa-4" Elevation="0" Style="border: 1px solid var(--mud-palette-divider);">
|
||||
<MudText Typo="Typo.caption" Class="text-muted mb-1">위험도</MudText>
|
||||
<MudText Typo="Typo.h5">중간</MudText>
|
||||
<MudChip T="string" Label="true" Size="Size.Small" Color="Color.Warning" Variant="Variant.Filled" Class="mt-1">
|
||||
Moderate
|
||||
</MudChip>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
|
||||
<!-- Asset Breakdown -->
|
||||
<MudGrid Spacing="3" Class="mb-6">
|
||||
<MudItem xs="12" md="8">
|
||||
<MudPaper Class="pa-4" Elevation="1">
|
||||
<MudText Typo="Typo.h6" Class="mb-4">자산 구성</MudText>
|
||||
|
||||
<MudTable Items="@_assets" Dense="true" Hover="true" Striped="true">
|
||||
<HeaderContent>
|
||||
<MudTh>종목/펀드명</MudTh>
|
||||
<MudTh>수량</MudTh>
|
||||
<MudTh>현재가</MudTh>
|
||||
<MudTh>평가액</MudTh>
|
||||
<MudTh>수익률</MudTh>
|
||||
<MudTh>비율</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd DataLabel="Name">
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<MudAvatar Size="Size.Small" Color="Color.Primary">@context.Name[0]</MudAvatar>
|
||||
<div>
|
||||
<MudText Typo="Typo.body2" Class="font-weight-500">@context.Name</MudText>
|
||||
<MudText Typo="Typo.caption" Class="text-muted">@context.Ticker</MudText>
|
||||
</div>
|
||||
</div>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Quantity">
|
||||
<MudText Typo="Typo.body2">@context.Quantity.ToString("N0")</MudText>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Price">
|
||||
<MudText Typo="Typo.body2">₩@context.CurrentPrice.ToString("N0")</MudText>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Value">
|
||||
<MudText Typo="Typo.body2" Class="font-weight-500">₩@context.Value.ToString("N0")</MudText>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Return">
|
||||
<MudChip T="string" Label="true" Size="Size.Small"
|
||||
Color="@(context.ReturnRate >= 0 ? Color.Success : Color.Error)"
|
||||
Variant="Variant.Filled">
|
||||
@(context.ReturnRate >= 0 ? "+" : "")@context.ReturnRate.ToString("F1")%
|
||||
</MudChip>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Ratio">
|
||||
<MudText Typo="Typo.body2">@context.Ratio.ToString("F1")%</MudText>
|
||||
</MudTd>
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12" md="4">
|
||||
<MudPaper Class="pa-4" Elevation="1">
|
||||
<MudText Typo="Typo.h6" Class="mb-4">자산 분류</MudText>
|
||||
|
||||
<MudStack Spacing="2">
|
||||
@foreach (var category in AssetCategories)
|
||||
{
|
||||
<div>
|
||||
<div class="d-flex justify-content-between mb-1">
|
||||
<MudText Typo="Typo.body2">@category.Name</MudText>
|
||||
<MudText Typo="Typo.body2" Class="font-weight-500">@category.Percentage%</MudText>
|
||||
</div>
|
||||
<MudProgressLinear Value="@category.Percentage" Color="@category.Color" />
|
||||
</div>
|
||||
}
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
|
||||
<!-- Trading History -->
|
||||
<MudPaper Class="pa-4" Elevation="1">
|
||||
<MudText Typo="Typo.h6" Class="mb-4">거래 이력</MudText>
|
||||
|
||||
@if (TradingHistory.Count == 0)
|
||||
{
|
||||
<MudAlert Severity="Severity.Info">거래 이력이 없습니다.</MudAlert>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudTable Items="@TradingHistory" Dense="true" Hover="true" Striped="true">
|
||||
<HeaderContent>
|
||||
<MudTh>일자</MudTh>
|
||||
<MudTh>종목</MudTh>
|
||||
<MudTh>구분</MudTh>
|
||||
<MudTh>수량</MudTh>
|
||||
<MudTh>단가</MudTh>
|
||||
<MudTh>금액</MudTh>
|
||||
<MudTh>수수료</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd DataLabel="Date">
|
||||
<MudText Typo="Typo.body2">@context.Date.ToString("yyyy-MM-dd")</MudText>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Ticker">
|
||||
<MudText Typo="Typo.body2">@context.Ticker</MudText>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Type">
|
||||
<MudChip T="string" Label="true" Size="Size.Small"
|
||||
Color="@(context.Type == "매수" ? Color.Success : Color.Error)"
|
||||
Variant="Variant.Filled">
|
||||
@context.Type
|
||||
</MudChip>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Quantity">
|
||||
<MudText Typo="Typo.body2">@context.Quantity</MudText>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Price">
|
||||
<MudText Typo="Typo.body2">₩@context.Price.ToString("N0")</MudText>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Amount">
|
||||
<MudText Typo="Typo.body2">₩@context.Amount.ToString("N0")</MudText>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Fee">
|
||||
<MudText Typo="Typo.body2" Class="text-muted">₩@context.Fee.ToString("N0")</MudText>
|
||||
</MudTd>
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
}
|
||||
</MudPaper>
|
||||
|
||||
@code {
|
||||
private List<AssetModel> _assets = new();
|
||||
private List<CategoryModel> AssetCategories = new();
|
||||
private List<TradeModel> TradingHistory = new();
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await LoadAssets();
|
||||
}
|
||||
|
||||
private async Task LoadAssets()
|
||||
{
|
||||
_assets = new List<AssetModel>
|
||||
{
|
||||
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<CategoryModel>
|
||||
{
|
||||
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<TradeModel>
|
||||
{
|
||||
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; }
|
||||
}
|
||||
}
|
||||
@@ -1,268 +0,0 @@
|
||||
@page "/users"
|
||||
@attribute [Authorize]
|
||||
@using MudBlazor
|
||||
@inject HttpClient Http
|
||||
@inject ISnackbar Snackbar
|
||||
@inject IDialogService DialogService
|
||||
|
||||
<PageTitle>QuantEngine - 사용자 관리</PageTitle>
|
||||
|
||||
<!-- Page Header -->
|
||||
<div class="mb-6">
|
||||
<MudText Typo="Typo.h4" Class="mb-2">사용자 관리</MudText>
|
||||
<MudText Typo="Typo.body1" Class="text-muted">시스템 사용자 및 권한 관리</MudText>
|
||||
</div>
|
||||
|
||||
<!-- Action Bar -->
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<MudTextField @bind-Value="SearchQuery" Placeholder="사용자 검색..."
|
||||
StartAdornment="@Icons.Material.Filled.Search"
|
||||
Style="width: 300px;" />
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="OpenAddUserDialog">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Add" Class="mr-2" />
|
||||
새 사용자 추가
|
||||
</MudButton>
|
||||
</div>
|
||||
|
||||
<!-- Users Table -->
|
||||
<MudPaper Class="pa-4" Elevation="1">
|
||||
@if (_users.Count == 0)
|
||||
{
|
||||
<MudAlert Severity="Severity.Info">사용자가 없습니다.</MudAlert>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudTable Items="@FilteredUsers" Dense="true" Hover="true" Striped="true">
|
||||
<HeaderContent>
|
||||
<MudTh>이름</MudTh>
|
||||
<MudTh>역할</MudTh>
|
||||
<MudTh>상태</MudTh>
|
||||
<MudTh>생성일</MudTh>
|
||||
<MudTh>수정일</MudTh>
|
||||
<MudTh>작업</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd DataLabel="Name">
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<MudAvatar Size="Size.Small" Color="Color.Primary">@context.Username[0].ToString().ToUpper()</MudAvatar>
|
||||
<MudText Typo="Typo.body2">@context.Username</MudText>
|
||||
</div>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Role">
|
||||
<MudChip T="string" Label="true" Size="Size.Small"
|
||||
Color="@(context.Role == "Admin" ? Color.Primary : (context.Role == "Operator" ? Color.Secondary : Color.Default))"
|
||||
Variant="Variant.Filled">
|
||||
@context.Role
|
||||
</MudChip>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Status">
|
||||
<MudChip T="string" Label="true" Size="Size.Small"
|
||||
Color="@(context.IsActive ? Color.Success : Color.Warning)"
|
||||
Variant="Variant.Filled">
|
||||
@(context.IsActive ? "활성" : "비활성")
|
||||
</MudChip>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Joined">
|
||||
<MudText Typo="Typo.body2">@FormatDate(context.CreatedAt)</MudText>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Updated">
|
||||
<MudText Typo="Typo.body2">@FormatDate(context.UpdatedAt)</MudText>
|
||||
</MudTd>
|
||||
<MudTd DataLabel="Actions">
|
||||
<MudButton Variant="Variant.Text" Size="Size.Small" Color="Color.Primary" OnClick="@(() => EditUser(context))">편집</MudButton>
|
||||
<MudButton Variant="Variant.Text" Size="Size.Small" Color="Color.Error" OnClick="@(() => DeleteUser(context))">삭제</MudButton>
|
||||
</MudTd>
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
}
|
||||
</MudPaper>
|
||||
|
||||
<!-- Add/Edit Dialog -->
|
||||
<MudDialog @bind-Visible="_dialogVisible" Options="_dialogOptions">
|
||||
<TitleContent>
|
||||
<MudText Typo="Typo.h6">
|
||||
<MudIcon Icon="@(_isEditMode ? Icons.Material.Filled.Edit : Icons.Material.Filled.Add)" Class="mr-3" />
|
||||
@(_isEditMode ? "사용자 편집" : "새 사용자 추가")
|
||||
</MudText>
|
||||
</TitleContent>
|
||||
<DialogContent>
|
||||
<MudForm Model="@_formModel" @ref="_form">
|
||||
<MudTextField T="string" @bind-Value="_formModel.Username" Label="사용자 ID" Required="true" Disabled="@_isEditMode"
|
||||
RequiredError="사용자 ID를 입력해 주세요." Class="mb-3" />
|
||||
|
||||
<MudTextField T="string" @bind-Value="_formModel.Password" Label="@(_isEditMode ? "새 비밀번호 (미입력시 유지)" : "비밀번호")"
|
||||
InputType="InputType.Password" Required="@(!_isEditMode)" RequiredError="비밀번호를 입력해 주세요." Class="mb-3" />
|
||||
|
||||
<MudSelect T="string" @bind-Value="_formModel.Role" Label="역할 권한" Required="true" Class="mb-3">
|
||||
<MudSelectItem Value="@("Admin")">Admin (관리자)</MudSelectItem>
|
||||
<MudSelectItem Value="@("Operator")">Operator (운영자)</MudSelectItem>
|
||||
<MudSelectItem Value="@("Viewer")">Viewer (조회자)</MudSelectItem>
|
||||
</MudSelect>
|
||||
|
||||
@if (_isEditMode)
|
||||
{
|
||||
<MudSwitch T="bool" @bind-Value="_formModel.IsActive" Color="Color.Success" Label="계정 활성화 상태" />
|
||||
}
|
||||
</MudForm>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton Variant="Variant.Text" Color="Color.Default" OnClick="CloseDialog" Class="px-5">취소</MudButton>
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="SaveUser" Class="px-5">저장</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
|
||||
@code {
|
||||
private List<UserDto> _users = new();
|
||||
private string SearchQuery = "";
|
||||
private bool _dialogVisible;
|
||||
private bool _isEditMode;
|
||||
private MudForm _form = new();
|
||||
private UserFormModel _formModel = new();
|
||||
private DialogOptions _dialogOptions = new() { MaxWidth = MaxWidth.Small, FullWidth = true, CloseButton = true };
|
||||
|
||||
private IEnumerable<UserDto> FilteredUsers
|
||||
{
|
||||
get => string.IsNullOrEmpty(SearchQuery)
|
||||
? _users
|
||||
: _users.Where(u => u.Username.Contains(SearchQuery, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await LoadUsers();
|
||||
}
|
||||
|
||||
private async Task LoadUsers()
|
||||
{
|
||||
try
|
||||
{
|
||||
// BaseAddress is set to HostEnvironment.BaseAddress by DI in Client/Program.cs.
|
||||
// Never override it with a hardcoded port.
|
||||
var res = await Http.GetFromJsonAsync<List<UserDto>>("api/users");
|
||||
if (res != null)
|
||||
{
|
||||
_users = res;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Snackbar.Add($"사용자 목록 로드 실패: {ex.Message}", Severity.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void OpenAddUserDialog()
|
||||
{
|
||||
_isEditMode = false;
|
||||
_formModel = new UserFormModel { Role = "Viewer", IsActive = true };
|
||||
_dialogVisible = true;
|
||||
}
|
||||
|
||||
private void EditUser(UserDto user)
|
||||
{
|
||||
_isEditMode = true;
|
||||
_formModel = new UserFormModel
|
||||
{
|
||||
Username = user.Username,
|
||||
Role = user.Role,
|
||||
IsActive = user.IsActive,
|
||||
Password = "" // Clear password field for security
|
||||
};
|
||||
_dialogVisible = true;
|
||||
}
|
||||
|
||||
private async Task DeleteUser(UserDto user)
|
||||
{
|
||||
bool? result = await DialogService.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;
|
||||
}
|
||||
}
|
||||
@@ -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<LocalStorageService>();
|
||||
|
||||
// App State Service (RBAC & global state management)
|
||||
builder.Services.AddScoped<AppStateService>();
|
||||
|
||||
// Authentication setup in WebAssembly client
|
||||
builder.Services.AddAuthorizationCore();
|
||||
builder.Services.AddCascadingAuthenticationState();
|
||||
builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthenticationStateProvider>();
|
||||
|
||||
// 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<ApiClient>();
|
||||
|
||||
await builder.Build().RunAsync();
|
||||
@@ -1,22 +0,0 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<NoDefaultLaunchSettingsFile>true</NoDefaultLaunchSettingsFile>
|
||||
<StaticWebAssetProjectMode>Default</StaticWebAssetProjectMode>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\QuantEngine.Core\QuantEngine.Core.csproj" />
|
||||
<ProjectReference Include="..\..\QuantEngine.Application\QuantEngine.Application.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="10.0.0" />
|
||||
<PackageReference Include="MudBlazor" Version="9.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -1,8 +0,0 @@
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
@code {
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
NavigationManager.NavigateTo("login");
|
||||
}
|
||||
}
|
||||
@@ -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<ApiClient> _logger;
|
||||
public ApiClient(HttpClient http, ILogger<ApiClient> 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<CollectionDashboardStateDto?> GetCollectionStateAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _http.GetFromJsonAsync<CollectionDashboardStateDto>("api/collection/state");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error fetching collection state");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<CollectionRunsResponse?> GetCollectionRunsAsync(int limit = 20)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _http.GetFromJsonAsync<CollectionRunsResponse>($"api/collection/runs?limit={limit}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error fetching collection runs");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<CollectionRunSnapshotsResponse?> GetCollectionSnapshotsAsync(string runId)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _http.GetFromJsonAsync<CollectionRunSnapshotsResponse>($"api/collection/runs/{runId}/snapshots");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"Error fetching snapshots for run {runId}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<CollectionRunErrorsResponse?> GetCollectionErrorsAsync(string runId, int limit = 50)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _http.GetFromJsonAsync<CollectionRunErrorsResponse>($"api/collection/runs/{runId}/errors?limit={limit}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"Error fetching errors for run {runId}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<CollectionRunStartResponse?> StartCollectionRunAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var response = await _http.PostAsJsonAsync("api/collection/run", new { });
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
return await response.Content.ReadFromJsonAsync<CollectionRunStartResponse>();
|
||||
}
|
||||
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<CollectionErrorDto> 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<CollectionRunDto> 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<CollectionSnapshotDto> 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<CollectionErrorDto> 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; } = "";
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
namespace QuantEngine.Web.Client.Services;
|
||||
|
||||
public class AppStateService
|
||||
{
|
||||
private UserContext _currentUser;
|
||||
private List<string> _userRoles = new();
|
||||
private bool _isInitialized = false;
|
||||
|
||||
public event Action OnStateChanged;
|
||||
|
||||
public UserContext CurrentUser
|
||||
{
|
||||
get => _currentUser;
|
||||
set
|
||||
{
|
||||
_currentUser = value;
|
||||
NotifyStateChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public List<string> UserRoles
|
||||
{
|
||||
get => _userRoles;
|
||||
set
|
||||
{
|
||||
_userRoles = value;
|
||||
NotifyStateChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsInitialized
|
||||
{
|
||||
get => _isInitialized;
|
||||
set
|
||||
{
|
||||
_isInitialized = value;
|
||||
NotifyStateChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public AppStateService()
|
||||
{
|
||||
_currentUser = new UserContext();
|
||||
_userRoles = new List<string>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize app state from current user context
|
||||
/// </summary>
|
||||
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<string> { "Admin" };
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Handle error
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if user has specific role (RBAC)
|
||||
/// </summary>
|
||||
public bool HasRole(string role)
|
||||
{
|
||||
return UserRoles.Contains(role);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if user has any of the specified roles
|
||||
/// </summary>
|
||||
public bool HasAnyRole(params string[] roles)
|
||||
{
|
||||
return roles.Any(r => UserRoles.Contains(r));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if user has all specified roles
|
||||
/// </summary>
|
||||
public bool HasAllRoles(params string[] roles)
|
||||
{
|
||||
return roles.All(r => UserRoles.Contains(r));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear user state
|
||||
/// </summary>
|
||||
public void Clear()
|
||||
{
|
||||
CurrentUser = new UserContext();
|
||||
UserRoles = new List<string>();
|
||||
IsInitialized = false;
|
||||
}
|
||||
|
||||
private void NotifyStateChanged() => OnStateChanged?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// User context model
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// API Response wrapper
|
||||
/// </summary>
|
||||
public class ApiResponse<T>
|
||||
{
|
||||
public bool Success { get; set; }
|
||||
public string Message { get; set; }
|
||||
public T Data { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pagination model
|
||||
/// </summary>
|
||||
public class PaginatedResponse<T>
|
||||
{
|
||||
public List<T> 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;
|
||||
}
|
||||
@@ -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<T>(string key, T value)
|
||||
{
|
||||
var json = JsonSerializer.Serialize(value);
|
||||
await _js.InvokeVoidAsync("localStorage.setItem", key, json);
|
||||
}
|
||||
|
||||
public async Task<T?> GetAsync<T>(string key)
|
||||
{
|
||||
try
|
||||
{
|
||||
var json = await _js.InvokeAsync<string?>("localStorage.getItem", key);
|
||||
if (string.IsNullOrEmpty(json))
|
||||
{
|
||||
return default;
|
||||
}
|
||||
return JsonSerializer.Deserialize<T>(json);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task DeleteAsync(string key)
|
||||
{
|
||||
await _js.InvokeVoidAsync("localStorage.removeItem", key);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -18,25 +18,10 @@
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Exclude client project files from server build to avoid duplicate compilations -->
|
||||
<!-- BUT preserve Client\wwwroot for static web assets -->
|
||||
<Compile Remove="Client\**" />
|
||||
<EmbeddedResource Remove="Client\**" />
|
||||
<None Remove="Client\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Only remove non-wwwroot Client content -->
|
||||
<Content Remove="Client\**" />
|
||||
<Content Include="Client\wwwroot\**" CopyToPublishDirectory="Never" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<BlazorDisableThrowNavigationException>true</BlazorDisableThrowNavigationException>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -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.<Main>$(String[] args) in C:\Temp\data_feed\src\dotnet\QuantEngine.Web\Program.cs:line 99
|
||||
@@ -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.<<StartAsync>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.<StartAsync>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.<<StartAsync>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.<StartAsync>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.<Main>$(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.<<StartAsync>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.<StartAsync>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.<<StartAsync>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.<StartAsync>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.<Main>$(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
|
||||
@@ -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.<<StartAsync>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.<StartAsync>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.<<StartAsync>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.<StartAsync>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.<Main>$(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<TResult>.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.<Open>g__OpenCore|209_0(NpgsqlConnector conn, String username, SslMode sslMode, GssEncryptionMode gssEncMode, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken)
|
||||
at Npgsql.Internal.NpgsqlConnector.<Open>g__OpenCore|209_0(NpgsqlConnector conn, String username, SslMode sslMode, GssEncryptionMode gssEncMode, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken)
|
||||
at Npgsql.Internal.NpgsqlConnector.<Open>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.<Get>g__RentAsync|33_0(NpgsqlConnection conn, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken)
|
||||
at Npgsql.NpgsqlConnection.<Open>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.<InvokeNextResourceFilter>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.<InvokeFilterPipelineAsync>g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
|
||||
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
|
||||
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
|
||||
at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>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<TResult>.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.<Open>g__OpenCore|209_0(NpgsqlConnector conn, String username, SslMode sslMode, GssEncryptionMode gssEncMode, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken)
|
||||
at Npgsql.Internal.NpgsqlConnector.<Open>g__OpenCore|209_0(NpgsqlConnector conn, String username, SslMode sslMode, GssEncryptionMode gssEncMode, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken)
|
||||
at Npgsql.Internal.NpgsqlConnector.<Open>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.<Get>g__RentAsync|33_0(NpgsqlConnection conn, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken)
|
||||
at Npgsql.NpgsqlConnection.<Open>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.<InvokeNextResourceFilter>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.<InvokeFilterPipelineAsync>g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
|
||||
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
|
||||
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
|
||||
at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>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<TResult>.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.<Open>g__OpenCore|209_0(NpgsqlConnector conn, String username, SslMode sslMode, GssEncryptionMode gssEncMode, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken)
|
||||
at Npgsql.Internal.NpgsqlConnector.<Open>g__OpenCore|209_0(NpgsqlConnector conn, String username, SslMode sslMode, GssEncryptionMode gssEncMode, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken)
|
||||
at Npgsql.Internal.NpgsqlConnector.<Open>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.<Get>g__RentAsync|33_0(NpgsqlConnection conn, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken)
|
||||
at Npgsql.NpgsqlConnection.<Open>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.<InvokeNextResourceFilter>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.<InvokeFilterPipelineAsync>g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
|
||||
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
|
||||
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
|
||||
at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>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<TResult>.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.<Open>g__OpenCore|209_0(NpgsqlConnector conn, String username, SslMode sslMode, GssEncryptionMode gssEncMode, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken)
|
||||
at Npgsql.Internal.NpgsqlConnector.<Open>g__OpenCore|209_0(NpgsqlConnector conn, String username, SslMode sslMode, GssEncryptionMode gssEncMode, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken)
|
||||
at Npgsql.Internal.NpgsqlConnector.<Open>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.<Get>g__RentAsync|33_0(NpgsqlConnection conn, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken)
|
||||
at Npgsql.NpgsqlConnection.<Open>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.<InvokeNextResourceFilter>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.<InvokeFilterPipelineAsync>g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
|
||||
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
|
||||
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
|
||||
at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>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
|
||||
Reference in New Issue
Block a user