Compare commits
4 Commits
8bd678c7c7
...
3e4d545e01
| Author | SHA1 | Date | |
|---|---|---|---|
| 3e4d545e01 | |||
| e4290ef3c6 | |||
| 4de9339163 | |||
| bdb9262f4e |
@@ -0,0 +1,63 @@
|
|||||||
|
using System.Security.Claims;
|
||||||
|
using Microsoft.AspNetCore.Components.Authorization;
|
||||||
|
using QuantEngine.Web.Client.Services;
|
||||||
|
|
||||||
|
namespace QuantEngine.Web.Client.Infrastructure
|
||||||
|
{
|
||||||
|
public class CustomAuthenticationStateProvider : AuthenticationStateProvider
|
||||||
|
{
|
||||||
|
private readonly LocalStorageService _localStorage;
|
||||||
|
private readonly ClaimsPrincipal _anonymous = new ClaimsPrincipal(new ClaimsIdentity());
|
||||||
|
private const string StorageKey = "quant_admin_session";
|
||||||
|
|
||||||
|
public CustomAuthenticationStateProvider(LocalStorageService localStorage)
|
||||||
|
{
|
||||||
|
_localStorage = localStorage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var username = await _localStorage.GetAsync<string>(StorageKey);
|
||||||
|
if (!string.IsNullOrEmpty(username))
|
||||||
|
{
|
||||||
|
var identity = new ClaimsIdentity(new[]
|
||||||
|
{
|
||||||
|
new Claim(ClaimTypes.Name, username),
|
||||||
|
new Claim(ClaimTypes.Role, "Admin")
|
||||||
|
}, "QuantAdminAuth");
|
||||||
|
|
||||||
|
var user = new ClaimsPrincipal(identity);
|
||||||
|
return new AuthenticationState(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Return anonymous if localStorage isn't ready
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AuthenticationState(_anonymous);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task MarkUserAsAuthenticatedAsync(string username)
|
||||||
|
{
|
||||||
|
await _localStorage.SetAsync(StorageKey, username);
|
||||||
|
|
||||||
|
var identity = new ClaimsIdentity(new[]
|
||||||
|
{
|
||||||
|
new Claim(ClaimTypes.Name, username),
|
||||||
|
new Claim(ClaimTypes.Role, "Admin")
|
||||||
|
}, "QuantAdminAuth");
|
||||||
|
|
||||||
|
var user = new ClaimsPrincipal(identity);
|
||||||
|
NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(user)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task MarkUserAsLoggedOutAsync()
|
||||||
|
{
|
||||||
|
await _localStorage.DeleteAsync(StorageKey);
|
||||||
|
NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(_anonymous)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+33
-18
@@ -1,8 +1,10 @@
|
|||||||
@inherits LayoutComponentBase
|
@inherits LayoutComponentBase
|
||||||
@inject Microsoft.AspNetCore.Hosting.IWebHostEnvironment WebHostEnvironment
|
@inject HttpClient Http
|
||||||
@using System.IO
|
@inject AuthenticationStateProvider AuthStateProvider
|
||||||
@using System.Text.Json
|
@inject NavigationManager NavigationManager
|
||||||
|
@using System.Net.Http.Json
|
||||||
@using Microsoft.FluentUI.AspNetCore.Components
|
@using Microsoft.FluentUI.AspNetCore.Components
|
||||||
|
@using QuantEngine.Web.Client.Infrastructure
|
||||||
|
|
||||||
<FluentStack Orientation="Orientation.Vertical" Class="h-100 w-100">
|
<FluentStack Orientation="Orientation.Vertical" Class="h-100 w-100">
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
@@ -15,6 +17,16 @@
|
|||||||
☰
|
☰
|
||||||
</FluentButton>
|
</FluentButton>
|
||||||
<h1 style="margin: 0; font-size: 20px; font-weight: 600;">QuantEngine v@appVersion</h1>
|
<h1 style="margin: 0; font-size: 20px; font-weight: 600;">QuantEngine v@appVersion</h1>
|
||||||
|
<AuthorizeView>
|
||||||
|
<Authorized>
|
||||||
|
<div style="margin-left: auto; display: flex; align-items: center; gap: 12px;">
|
||||||
|
<span style="font-size: 13px; color: var(--neutral-foreground-hint);">관리자 (@context.User.Identity?.Name)</span>
|
||||||
|
<FluentButton OnClick="HandleLogoutAsync" Style="color: #ff5252; background: transparent; border: 1px solid rgba(255, 82, 82, 0.2); cursor: pointer; padding: 4px 12px; border-radius: 4px;">
|
||||||
|
로그아웃
|
||||||
|
</FluentButton>
|
||||||
|
</div>
|
||||||
|
</Authorized>
|
||||||
|
</AuthorizeView>
|
||||||
</FluentStack>
|
</FluentStack>
|
||||||
</FluentHeader>
|
</FluentHeader>
|
||||||
|
|
||||||
@@ -64,25 +76,15 @@
|
|||||||
private string appVersion = "Local Debug";
|
private string appVersion = "Local Debug";
|
||||||
private string buildTime = "N/A";
|
private string buildTime = "N/A";
|
||||||
|
|
||||||
protected override void OnInitialized()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var versionFilePath = Path.Combine(WebHostEnvironment.WebRootPath, "version.json");
|
var versionInfo = await Http.GetFromJsonAsync<VersionInfo>("version.json");
|
||||||
if (File.Exists(versionFilePath))
|
if (versionInfo != null)
|
||||||
{
|
{
|
||||||
var jsonContent = File.ReadAllText(versionFilePath);
|
appVersion = versionInfo.Version ?? "Local Debug";
|
||||||
using var doc = System.Text.Json.JsonDocument.Parse(jsonContent);
|
buildTime = versionInfo.Built ?? "N/A";
|
||||||
var root = doc.RootElement;
|
|
||||||
|
|
||||||
if (root.TryGetProperty("version", out var versionProp))
|
|
||||||
{
|
|
||||||
appVersion = versionProp.GetString() ?? "Local Debug";
|
|
||||||
}
|
|
||||||
if (root.TryGetProperty("built", out var builtProp))
|
|
||||||
{
|
|
||||||
buildTime = builtProp.GetString() ?? "N/A";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
@@ -90,5 +92,18 @@
|
|||||||
// Fail-safe default fallback values
|
// Fail-safe default fallback values
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task HandleLogoutAsync()
|
||||||
|
{
|
||||||
|
var customProvider = (CustomAuthenticationStateProvider)AuthStateProvider;
|
||||||
|
await customProvider.MarkUserAsLoggedOutAsync();
|
||||||
|
NavigationManager.NavigateTo("login");
|
||||||
|
}
|
||||||
|
|
||||||
|
private class VersionInfo
|
||||||
|
{
|
||||||
|
public string? Version { get; set; }
|
||||||
|
public string? Built { get; set; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
@page "/collection"
|
@page "/collection"
|
||||||
@using QuantEngine.Web.Services
|
@attribute [Authorize]
|
||||||
|
@using QuantEngine.Web.Client.Services
|
||||||
@inject ApiClient ApiClient
|
@inject ApiClient ApiClient
|
||||||
@inject ILogger<Collection> Logger
|
@inject ILogger<Collection> Logger
|
||||||
|
|
||||||
|
|||||||
+22
-10
@@ -1,6 +1,7 @@
|
|||||||
@page "/"
|
@page "/"
|
||||||
|
@attribute [Authorize]
|
||||||
@using QuantEngine.Core.Infrastructure
|
@using QuantEngine.Core.Infrastructure
|
||||||
@inject IWebHostEnvironment Environment
|
@inject HttpClient Http
|
||||||
|
|
||||||
<PageTitle>Quant Engine - Dashboard</PageTitle>
|
<PageTitle>Quant Engine - Dashboard</PageTitle>
|
||||||
|
|
||||||
@@ -96,15 +97,26 @@
|
|||||||
private string RawFeedLabel = "DISCONNECTED";
|
private string RawFeedLabel = "DISCONNECTED";
|
||||||
private string ReportPath = "n/a";
|
private string ReportPath = "n/a";
|
||||||
|
|
||||||
protected override void OnInitialized()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
ReportPath = Path.GetFullPath(Path.Combine(Environment.ContentRootPath, "..", "..", "..", "Temp", "operational_report.json"));
|
try
|
||||||
var report = OperationalReportLoader.Load(ReportPath);
|
{
|
||||||
Sections.AddRange(report.Sections);
|
var report = await Http.GetFromJsonAsync<OperationalReportData>("api/operational-report");
|
||||||
SectionCountLabel = report.SectionCount.ToString();
|
if (report != null)
|
||||||
GeneratedAtLabel = report.GeneratedAt;
|
{
|
||||||
SourceLabel = report.SourceJson;
|
Sections.Clear();
|
||||||
ReportStateLabel = Sections.Count > 0 ? "READY" : "DATA_MISSING";
|
Sections.AddRange(report.Sections);
|
||||||
ReportChipLabel = Sections.Count > 0 ? "READY" : "DATA_MISSING";
|
SectionCountLabel = report.SectionCount.ToString();
|
||||||
|
GeneratedAtLabel = report.GeneratedAt;
|
||||||
|
SourceLabel = report.SourceJson;
|
||||||
|
ReportStateLabel = Sections.Count > 0 ? "READY" : "DATA_MISSING";
|
||||||
|
ReportChipLabel = Sections.Count > 0 ? "READY" : "DATA_MISSING";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
ReportStateLabel = "DATA_MISSING";
|
||||||
|
ReportChipLabel = "DATA_MISSING";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,308 @@
|
|||||||
|
@page "/login"
|
||||||
|
@attribute [AllowAnonymous]
|
||||||
|
@inject AuthenticationStateProvider AuthStateProvider
|
||||||
|
@inject NavigationManager NavigationManager
|
||||||
|
@inject HttpClient Http
|
||||||
|
|
||||||
|
<PageTitle>로그인 - QuantEngine</PageTitle>
|
||||||
|
|
||||||
|
<div class="auth-container">
|
||||||
|
<div class="auth-card">
|
||||||
|
<div class="brand-section">
|
||||||
|
<img src="images/quant_engine_logo.jpg" alt="QuantEngine Logo" class="brand-logo" />
|
||||||
|
<h1 class="brand-title">QuantEngine</h1>
|
||||||
|
<p class="brand-subtitle">은퇴자산포트폴리오 투자 관리 시스템</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form @onsubmit="HandleLoginAsync" class="auth-form">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="username">관리자 아이디</label>
|
||||||
|
<input type="text" id="username" class="form-control" @bind="Username" placeholder="아이디를 입력하세요" autocomplete="username" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password">비밀번호</label>
|
||||||
|
<input type="password" id="password" class="form-control" @bind="Password" placeholder="비밀번호를 입력하세요" autocomplete="current-password" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (!string.IsNullOrEmpty(ErrorMessage))
|
||||||
|
{
|
||||||
|
<div class="error-message">
|
||||||
|
<svg class="error-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
||||||
|
</svg>
|
||||||
|
<span>@ErrorMessage</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<button type="submit" class="btn-submit" disabled="@IsSubmitting">
|
||||||
|
@if (IsSubmitting)
|
||||||
|
{
|
||||||
|
<span class="spinner"></span>
|
||||||
|
<span>인증 중...</span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span>로그인</span>
|
||||||
|
}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.auth-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
background: linear-gradient(135deg, #090a15 0%, #12142d 100%);
|
||||||
|
font-family: 'Roboto', 'Inter', sans-serif;
|
||||||
|
color: #ffffff;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 9999;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ambient background glow */
|
||||||
|
.auth-container::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
width: 600px;
|
||||||
|
height: 600px;
|
||||||
|
background: radial-gradient(circle, rgba(0, 242, 254, 0.08) 0%, rgba(79, 172, 254, 0) 70%);
|
||||||
|
top: -10%;
|
||||||
|
left: -10%;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-container::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
width: 600px;
|
||||||
|
height: 600px;
|
||||||
|
background: radial-gradient(circle, rgba(79, 172, 254, 0.08) 0%, rgba(0, 242, 254, 0) 70%);
|
||||||
|
bottom: -10%;
|
||||||
|
right: -10%;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-card {
|
||||||
|
background: rgba(255, 255, 255, 0.02);
|
||||||
|
backdrop-filter: blur(25px);
|
||||||
|
-webkit-backdrop-filter: blur(25px);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.06);
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 48px;
|
||||||
|
width: 440px;
|
||||||
|
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.4);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 10;
|
||||||
|
animation: fadeIn 0.8s cubic-bezier(0.16, 1, 0.3, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-section {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 36px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-logo {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
border-radius: 50%;
|
||||||
|
object-fit: cover;
|
||||||
|
border: 2px solid rgba(0, 242, 254, 0.3);
|
||||||
|
box-shadow: 0 0 20px rgba(0, 242, 254, 0.15);
|
||||||
|
margin-bottom: 16px;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-logo:hover {
|
||||||
|
transform: rotate(5deg) scale(1.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-title {
|
||||||
|
font-size: 26px;
|
||||||
|
font-weight: 700;
|
||||||
|
margin: 0;
|
||||||
|
background: linear-gradient(90deg, #00f2fe 0%, #4facfe 100%);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-subtitle {
|
||||||
|
font-size: 13px;
|
||||||
|
color: rgba(255, 255, 255, 0.5);
|
||||||
|
margin: 6px 0 0 0;
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
.auth-form {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group label {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: rgba(255, 255, 255, 0.8);
|
||||||
|
padding-left: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control {
|
||||||
|
background: rgba(255, 255, 255, 0.04);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 14px 16px;
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 14px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control:focus {
|
||||||
|
border-color: rgba(0, 242, 254, 0.6);
|
||||||
|
background: rgba(255, 255, 255, 0.08);
|
||||||
|
box-shadow: 0 0 12px rgba(0, 242, 254, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control::placeholder {
|
||||||
|
color: rgba(255, 255, 255, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
background: rgba(239, 68, 68, 0.08);
|
||||||
|
border: 1px solid rgba(239, 68, 68, 0.2);
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
color: #f87171;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-icon {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-submit {
|
||||||
|
background: linear-gradient(90deg, #00f2fe 0%, #4facfe 100%);
|
||||||
|
border: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 14px;
|
||||||
|
color: #0b0c15;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 700;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 10px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
box-shadow: 0 4px 15px rgba(0, 242, 254, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-submit:hover:not(:disabled) {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 6px 20px rgba(0, 242, 254, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-submit:active:not(:disabled) {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-submit:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border: 2px solid rgba(11, 12, 21, 0.25);
|
||||||
|
border-top-color: #0b0c15;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 0.8s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@@keyframes spin {
|
||||||
|
to { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(20px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private string Username { get; set; } = string.Empty;
|
||||||
|
private string Password { get; set; } = string.Empty;
|
||||||
|
private string ErrorMessage { get; set; } = string.Empty;
|
||||||
|
private bool IsSubmitting { get; set; } = false;
|
||||||
|
|
||||||
|
private async Task HandleLoginAsync()
|
||||||
|
{
|
||||||
|
ErrorMessage = string.Empty;
|
||||||
|
if (string.IsNullOrWhiteSpace(Username) || string.IsNullOrWhiteSpace(Password))
|
||||||
|
{
|
||||||
|
ErrorMessage = "아이디와 비밀번호를 모두 입력해 주세요.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
IsSubmitting = true;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var response = await Http.PostAsJsonAsync("api/auth/login", new { Username, Password });
|
||||||
|
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var customProvider = (CustomAuthenticationStateProvider)AuthStateProvider;
|
||||||
|
await customProvider.MarkUserAsAuthenticatedAsync(Username);
|
||||||
|
|
||||||
|
// Redirect back to home dashboard
|
||||||
|
NavigationManager.NavigateTo("");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ErrorMessage = "아이디 또는 비밀번호가 올바르지 않습니다.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
ErrorMessage = $"로그인 중 오류가 발생했습니다: {ex.Message}";
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
IsSubmitting = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+24
-13
@@ -1,6 +1,7 @@
|
|||||||
@page "/operations"
|
@page "/operations"
|
||||||
|
@attribute [Authorize]
|
||||||
@using QuantEngine.Core.Infrastructure
|
@using QuantEngine.Core.Infrastructure
|
||||||
@inject IWebHostEnvironment Environment
|
@inject HttpClient Http
|
||||||
|
|
||||||
<PageTitle>Quant Engine - Operations</PageTitle>
|
<PageTitle>Quant Engine - Operations</PageTitle>
|
||||||
|
|
||||||
@@ -97,19 +98,29 @@
|
|||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
ReportPath = Path.GetFullPath(Path.Combine(Environment.ContentRootPath, "..", "..", "..", "Temp", "operational_report.json"));
|
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);
|
||||||
|
|
||||||
var report = OperationalReportLoader.Load(ReportPath);
|
HighlightSections.Clear();
|
||||||
SchemaVersion = report.SchemaVersion;
|
HighlightSections.AddRange(Sections.Take(4));
|
||||||
SourceJson = report.SourceJson;
|
|
||||||
GeneratedAt = report.GeneratedAt;
|
|
||||||
Sections.AddRange(report.Sections);
|
|
||||||
|
|
||||||
HighlightSections.Clear();
|
SectionCountLabel = report.SectionCount.ToString();
|
||||||
HighlightSections.AddRange(Sections.Take(4));
|
RenderedSectionCountLabel = Sections.Count.ToString();
|
||||||
|
HealthLabel = Sections.Count > 0 ? "PASS" : "DATA_MISSING";
|
||||||
SectionCountLabel = report.SectionCount.ToString();
|
}
|
||||||
RenderedSectionCountLabel = Sections.Count.ToString();
|
}
|
||||||
HealthLabel = Sections.Count > 0 ? "PASS" : "DATA_MISSING";
|
catch
|
||||||
|
{
|
||||||
|
HealthLabel = "DATA_MISSING";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
||||||
|
using Microsoft.FluentUI.AspNetCore.Components;
|
||||||
|
using Microsoft.AspNetCore.Components.Authorization;
|
||||||
|
using QuantEngine.Web.Client.Services;
|
||||||
|
using QuantEngine.Web.Client.Infrastructure;
|
||||||
|
|
||||||
|
var builder = WebAssemblyHostBuilder.CreateDefault(args);
|
||||||
|
|
||||||
|
// Register Fluent UI
|
||||||
|
builder.Services.AddFluentUIComponents();
|
||||||
|
|
||||||
|
// Register LocalStorage for cross-platform session persistence
|
||||||
|
builder.Services.AddScoped<LocalStorageService>();
|
||||||
|
|
||||||
|
// Authentication setup in WebAssembly client
|
||||||
|
builder.Services.AddAuthorizationCore();
|
||||||
|
builder.Services.AddCascadingAuthenticationState();
|
||||||
|
builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthenticationStateProvider>();
|
||||||
|
|
||||||
|
// HttpClient register (API-First standard)
|
||||||
|
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
|
||||||
|
|
||||||
|
await builder.Build().RunAsync();
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<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-preview.2.25120.18" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="10.0.0-preview.2.25120.18" />
|
||||||
|
<PackageReference Include="Microsoft.FluentUI.AspNetCore.Components" Version="5.0.0-rc.4-26177.1" />
|
||||||
|
<PackageReference Include="Microsoft.FluentUI.AspNetCore.Components.Icons" Version="5.0.0-rc.4-26177.1" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
@inject NavigationManager NavigationManager
|
||||||
|
|
||||||
|
@code {
|
||||||
|
protected override void OnInitialized()
|
||||||
|
{
|
||||||
|
NavigationManager.NavigateTo("login");
|
||||||
|
}
|
||||||
|
}
|
||||||
+6
-9
@@ -2,19 +2,16 @@ using System.Net.Http.Json;
|
|||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using QuantEngine.Core.Interfaces;
|
using QuantEngine.Core.Interfaces;
|
||||||
|
|
||||||
namespace QuantEngine.Web.Services;
|
namespace QuantEngine.Web.Client.Services;
|
||||||
|
|
||||||
public class ApiClient
|
public class ApiClient
|
||||||
{
|
{
|
||||||
private readonly HttpClient _http;
|
private readonly HttpClient _http;
|
||||||
private readonly ILogger<ApiClient> _logger;
|
private readonly ILogger<ApiClient> _logger;
|
||||||
private string BaseUrl { get; set; }
|
|
||||||
|
|
||||||
public ApiClient(HttpClient http, ILogger<ApiClient> logger)
|
public ApiClient(HttpClient http, ILogger<ApiClient> logger)
|
||||||
{
|
{
|
||||||
_http = http;
|
_http = http;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
BaseUrl = "http://localhost:5001"; // Default for Blazor Server
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collection API Methods
|
// Collection API Methods
|
||||||
@@ -23,7 +20,7 @@ public class ApiClient
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return await _http.GetFromJsonAsync<CollectionDashboardStateDto>($"{BaseUrl}/api/collection/state");
|
return await _http.GetFromJsonAsync<CollectionDashboardStateDto>("api/collection/state");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -36,7 +33,7 @@ public class ApiClient
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return await _http.GetFromJsonAsync<CollectionRunsResponse>($"{BaseUrl}/api/collection/runs?limit={limit}");
|
return await _http.GetFromJsonAsync<CollectionRunsResponse>($"api/collection/runs?limit={limit}");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -49,7 +46,7 @@ public class ApiClient
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return await _http.GetFromJsonAsync<CollectionRunSnapshotsResponse>($"{BaseUrl}/api/collection/runs/{runId}/snapshots");
|
return await _http.GetFromJsonAsync<CollectionRunSnapshotsResponse>($"api/collection/runs/{runId}/snapshots");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -62,7 +59,7 @@ public class ApiClient
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return await _http.GetFromJsonAsync<CollectionRunErrorsResponse>($"{BaseUrl}/api/collection/runs/{runId}/errors?limit={limit}");
|
return await _http.GetFromJsonAsync<CollectionRunErrorsResponse>($"api/collection/runs/{runId}/errors?limit={limit}");
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -75,7 +72,7 @@ public class ApiClient
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var response = await _http.PostAsJsonAsync($"{BaseUrl}/api/collection/run", new { });
|
var response = await _http.PostAsJsonAsync("api/collection/run", new { });
|
||||||
if (response.IsSuccessStatusCode)
|
if (response.IsSuccessStatusCode)
|
||||||
{
|
{
|
||||||
return await response.Content.ReadFromJsonAsync<CollectionRunStartResponse>();
|
return await response.Content.ReadFromJsonAsync<CollectionRunStartResponse>();
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,7 +8,10 @@
|
|||||||
@using Microsoft.JSInterop
|
@using Microsoft.JSInterop
|
||||||
@using Microsoft.FluentUI.AspNetCore.Components
|
@using Microsoft.FluentUI.AspNetCore.Components
|
||||||
@using Microsoft.FluentUI.AspNetCore.Components.Icons
|
@using Microsoft.FluentUI.AspNetCore.Components.Icons
|
||||||
@using QuantEngine.Web
|
@using QuantEngine.Web.Client
|
||||||
@using QuantEngine.Web.Components
|
@using QuantEngine.Web.Client.Pages
|
||||||
@using QuantEngine.Web.Components.Layout
|
@using QuantEngine.Web.Client.Layout
|
||||||
@using QuantEngine.Web.Services
|
@using QuantEngine.Web.Client.Infrastructure
|
||||||
|
@using QuantEngine.Web.Client.Services
|
||||||
|
@using Microsoft.AspNetCore.Components.Authorization
|
||||||
|
@using Microsoft.AspNetCore.Authorization
|
||||||
|
|||||||
@@ -13,12 +13,12 @@
|
|||||||
<link rel="stylesheet" href="@Assets["QuantEngine.Web.styles.css"]" />
|
<link rel="stylesheet" href="@Assets["QuantEngine.Web.styles.css"]" />
|
||||||
<ImportMap />
|
<ImportMap />
|
||||||
<link rel="icon" type="image/png" href="favicon.png" />
|
<link rel="icon" type="image/png" href="favicon.png" />
|
||||||
<HeadOutlet />
|
<HeadOutlet @rendermode="InteractiveWebAssembly" />
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<FluentDesignSystemProvider>
|
<FluentDesignSystemProvider>
|
||||||
<Routes />
|
<Routes @rendermode="InteractiveWebAssembly" />
|
||||||
<ReconnectModal />
|
<ReconnectModal />
|
||||||
</FluentDesignSystemProvider>
|
</FluentDesignSystemProvider>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,16 @@
|
|||||||
<Router AppAssembly="typeof(Program).Assembly" NotFoundPage="typeof(Pages.NotFound)">
|
@using QuantEngine.Web.Client
|
||||||
<Found Context="routeData">
|
@using QuantEngine.Web.Client.Pages
|
||||||
<RouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)" />
|
@using QuantEngine.Web.Client.Layout
|
||||||
<FocusOnNavigate RouteData="routeData" Selector="h1" />
|
|
||||||
</Found>
|
<CascadingAuthenticationState>
|
||||||
</Router>
|
<Router AppAssembly="typeof(Dashboard).Assembly" NotFoundPage="typeof(NotFound)">
|
||||||
|
<Found Context="routeData">
|
||||||
|
<AuthorizeRouteView RouteData="routeData" DefaultLayout="typeof(MainLayout)">
|
||||||
|
<NotAuthorized>
|
||||||
|
<RedirectToLogin />
|
||||||
|
</NotAuthorized>
|
||||||
|
</AuthorizeRouteView>
|
||||||
|
<FocusOnNavigate RouteData="routeData" Selector="h1" />
|
||||||
|
</Found>
|
||||||
|
</Router>
|
||||||
|
</CascadingAuthenticationState>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@using System.Net.Http
|
@using System.Net.Http
|
||||||
@using System.Net.Http.Json
|
@using System.Net.Http.Json
|
||||||
@using Microsoft.AspNetCore.Components.Forms
|
@using Microsoft.AspNetCore.Components.Forms
|
||||||
@using Microsoft.AspNetCore.Components.Routing
|
@using Microsoft.AspNetCore.Components.Routing
|
||||||
@@ -11,3 +11,6 @@
|
|||||||
@using QuantEngine.Web
|
@using QuantEngine.Web
|
||||||
@using QuantEngine.Web.Components
|
@using QuantEngine.Web.Components
|
||||||
@using QuantEngine.Web.Components.Layout
|
@using QuantEngine.Web.Components.Layout
|
||||||
|
@using Microsoft.AspNetCore.Components.Authorization
|
||||||
|
@using Microsoft.AspNetCore.Authorization
|
||||||
|
@using QuantEngine.Web.Infrastructure
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ public static class CollectionEndpoints
|
|||||||
var runId = Guid.NewGuid().ToString("N");
|
var runId = Guid.NewGuid().ToString("N");
|
||||||
var now = DateTime.UtcNow.ToString("o");
|
var now = DateTime.UtcNow.ToString("o");
|
||||||
|
|
||||||
var body = await request.ReadAsAsync<CollectionRunRequest>();
|
var body = await request.ReadFromJsonAsync<CollectionRunRequest>();
|
||||||
var account = body?.Account ?? "real";
|
var account = body?.Account ?? "real";
|
||||||
var tickers = body?.Tickers ?? new List<string> { "005930", "000660" };
|
var tickers = body?.Tickers ?? new List<string> { "005930", "000660" };
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using QuantEngine.Web.Components;
|
using QuantEngine.Web.Components;
|
||||||
using QuantEngine.Web.Services;
|
|
||||||
using QuantEngine.Infrastructure.Data;
|
using QuantEngine.Infrastructure.Data;
|
||||||
|
using Microsoft.AspNetCore.Components.Authorization;
|
||||||
|
using QuantEngine.Web.Infrastructure;
|
||||||
using QuantEngine.Infrastructure.Repositories;
|
using QuantEngine.Infrastructure.Repositories;
|
||||||
using QuantEngine.Infrastructure.Services;
|
using QuantEngine.Infrastructure.Services;
|
||||||
using QuantEngine.Core.Interfaces;
|
using QuantEngine.Core.Interfaces;
|
||||||
@@ -9,7 +10,8 @@ using System.Text.Json;
|
|||||||
using static QuantEngine.Application.Services.DataCollectionService;
|
using static QuantEngine.Application.Services.DataCollectionService;
|
||||||
using Microsoft.FluentUI.AspNetCore.Components;
|
using Microsoft.FluentUI.AspNetCore.Components;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using QuantEngine.Web.Infrastructure;
|
using QuantEngine.Web.Client.Infrastructure;
|
||||||
|
using QuantEngine.Web.Client.Services;
|
||||||
using QuantEngine.Web.Endpoints;
|
using QuantEngine.Web.Endpoints;
|
||||||
|
|
||||||
// Serilog Configuration with Telegram Sink
|
// Serilog Configuration with Telegram Sink
|
||||||
@@ -24,7 +26,13 @@ builder.Host.UseSerilog();
|
|||||||
|
|
||||||
// Add services to the container.
|
// Add services to the container.
|
||||||
builder.Services.AddRazorComponents()
|
builder.Services.AddRazorComponents()
|
||||||
.AddInteractiveServerComponents();
|
.AddInteractiveWebAssemblyComponents();
|
||||||
|
|
||||||
|
// Authentication and Custom State Provider (Shared client components)
|
||||||
|
builder.Services.AddCascadingAuthenticationState();
|
||||||
|
builder.Services.AddScoped<LocalStorageService>();
|
||||||
|
builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthenticationStateProvider>();
|
||||||
|
builder.Services.AddAuthorizationCore();
|
||||||
|
|
||||||
// Fluent UI Services
|
// Fluent UI Services
|
||||||
builder.Services.AddFluentUIComponents();
|
builder.Services.AddFluentUIComponents();
|
||||||
@@ -94,6 +102,32 @@ app.MapStaticAssets();
|
|||||||
// Collection API Endpoints (must be before MapRazorComponents)
|
// Collection API Endpoints (must be before MapRazorComponents)
|
||||||
app.MapCollectionEndpoints();
|
app.MapCollectionEndpoints();
|
||||||
|
|
||||||
|
// Login API (API-First for Blazor WASM client authentication)
|
||||||
|
app.MapPost("/api/auth/login", (LoginRequest request, IConfiguration config) =>
|
||||||
|
{
|
||||||
|
var expectedUser = config["AdminSettings:Username"] ?? "admin";
|
||||||
|
var expectedPass = config["AdminSettings:Password"] ?? "quant123!";
|
||||||
|
|
||||||
|
if (request.Username == expectedUser && request.Password == expectedPass)
|
||||||
|
{
|
||||||
|
return Results.Ok(new { success = true, username = request.Username });
|
||||||
|
}
|
||||||
|
return Results.Json(new { success = false, error = "invalid_credentials" }, statusCode: 401);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Operational Report serving API (WASM safe file loading substitute)
|
||||||
|
app.MapGet("/api/operational-report", async (IWebHostEnvironment env) =>
|
||||||
|
{
|
||||||
|
var path = Path.GetFullPath(Path.Combine(env.ContentRootPath, "..", "..", "..", "Temp", "operational_report.json"));
|
||||||
|
if (!File.Exists(path))
|
||||||
|
{
|
||||||
|
return Results.NotFound(new { gate = "FAIL", error = "operational_report_missing" });
|
||||||
|
}
|
||||||
|
var json = await File.ReadAllTextAsync(path);
|
||||||
|
using var doc = JsonDocument.Parse(json);
|
||||||
|
return Results.Ok(doc.RootElement);
|
||||||
|
});
|
||||||
|
|
||||||
app.MapGet("/api/history/{domain}", async (string domain, int? limit, IPostgresqlHistorySnapshotReader reader) =>
|
app.MapGet("/api/history/{domain}", async (string domain, int? limit, IPostgresqlHistorySnapshotReader reader) =>
|
||||||
{
|
{
|
||||||
var rows = await reader.ReadAsync(domain, limit ?? 500);
|
var rows = await reader.ReadAsync(domain, limit ?? 500);
|
||||||
@@ -138,7 +172,14 @@ app.MapPost("/api/history/{domain}", async (string domain, JsonElement payload,
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.MapRazorComponents<App>()
|
app.MapRazorComponents<App>()
|
||||||
.AddInteractiveServerRenderMode();
|
.AddInteractiveWebAssemblyRenderMode()
|
||||||
|
.AddAdditionalAssemblies(typeof(QuantEngine.Web.Client._Imports).Assembly);
|
||||||
|
|
||||||
app.Run();
|
app.Run();
|
||||||
|
|
||||||
|
public class LoginRequest
|
||||||
|
{
|
||||||
|
public string Username { get; set; } = "";
|
||||||
|
public string Password { get; set; } = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,12 +4,22 @@
|
|||||||
<ProjectReference Include="..\QuantEngine.Infrastructure\QuantEngine.Infrastructure.csproj" />
|
<ProjectReference Include="..\QuantEngine.Infrastructure\QuantEngine.Infrastructure.csproj" />
|
||||||
<ProjectReference Include="..\QuantEngine.Application\QuantEngine.Application.csproj" />
|
<ProjectReference Include="..\QuantEngine.Application\QuantEngine.Application.csproj" />
|
||||||
<ProjectReference Include="..\QuantEngine.Core\QuantEngine.Core.csproj" />
|
<ProjectReference Include="..\QuantEngine.Core\QuantEngine.Core.csproj" />
|
||||||
|
<ProjectReference Include="Client\QuantEngine.Web.Client.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.FluentUI.AspNetCore.Components" Version="5.0.0-rc.4-26177.1" />
|
<PackageReference Include="Microsoft.FluentUI.AspNetCore.Components" Version="5.0.0-rc.4-26177.1" />
|
||||||
<PackageReference Include="Microsoft.FluentUI.AspNetCore.Components.Icons" Version="5.0.0-rc.4-26177.1" />
|
<PackageReference Include="Microsoft.FluentUI.AspNetCore.Components.Icons" Version="5.0.0-rc.4-26177.1" />
|
||||||
<PackageReference Include="Serilog.AspNetCore" Version="10.0.0" />
|
<PackageReference Include="Serilog.AspNetCore" Version="10.0.0" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="10.0.0-preview.2.25120.18" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<!-- Exclude client project files from server build to avoid duplicate compilations -->
|
||||||
|
<Compile Remove="Client\**" />
|
||||||
|
<Content Remove="Client\**" />
|
||||||
|
<EmbeddedResource Remove="Client\**" />
|
||||||
|
<None Remove="Client\**" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
|||||||
@@ -8,5 +8,9 @@
|
|||||||
"AllowedHosts": "*",
|
"AllowedHosts": "*",
|
||||||
"ConnectionStrings": {
|
"ConnectionStrings": {
|
||||||
"DefaultConnection": "Host=127.0.0.1;Database=giteadb;Username=gitea;Password=;Search Path=quantengine;"
|
"DefaultConnection": "Host=127.0.0.1;Database=giteadb;Username=gitea;Password=;Search Path=quantengine;"
|
||||||
|
},
|
||||||
|
"AdminSettings": {
|
||||||
|
"Username": "admin",
|
||||||
|
"Password": "quant123!"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 679 KiB |
@@ -1,4 +1,4 @@
|
|||||||
|
|
||||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
# Visual Studio Version 17
|
# Visual Studio Version 17
|
||||||
VisualStudioVersion = 17.0.31903.59
|
VisualStudioVersion = 17.0.31903.59
|
||||||
@@ -15,6 +15,9 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuantEngine.Core.Tests", "Q
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuantEngine.Tools", "QuantEngine.Tools\QuantEngine.Tools.csproj", "{E2B1A0A1-7B13-4A4D-9B31-9C7F4F27A6A1}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuantEngine.Tools", "QuantEngine.Tools\QuantEngine.Tools.csproj", "{E2B1A0A1-7B13-4A4D-9B31-9C7F4F27A6A1}"
|
||||||
EndProject
|
EndProject
|
||||||
|
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuantEngine.Web.Client", "QuantEngine.Web\Client\QuantEngine.Web.Client.csproj", "{C5F2F3BD-1258-40FC-803A-EE7EEC928107}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@@ -97,8 +100,21 @@ Global
|
|||||||
{E2B1A0A1-7B13-4A4D-9B31-9C7F4F27A6A1}.Release|x64.Build.0 = Release|Any CPU
|
{E2B1A0A1-7B13-4A4D-9B31-9C7F4F27A6A1}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{E2B1A0A1-7B13-4A4D-9B31-9C7F4F27A6A1}.Release|x86.ActiveCfg = Release|Any CPU
|
{E2B1A0A1-7B13-4A4D-9B31-9C7F4F27A6A1}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{E2B1A0A1-7B13-4A4D-9B31-9C7F4F27A6A1}.Release|x86.Build.0 = Release|Any CPU
|
{E2B1A0A1-7B13-4A4D-9B31-9C7F4F27A6A1}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{C5F2F3BD-1258-40FC-803A-EE7EEC928107}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{C5F2F3BD-1258-40FC-803A-EE7EEC928107}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{C5F2F3BD-1258-40FC-803A-EE7EEC928107}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{C5F2F3BD-1258-40FC-803A-EE7EEC928107}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{C5F2F3BD-1258-40FC-803A-EE7EEC928107}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{C5F2F3BD-1258-40FC-803A-EE7EEC928107}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{C5F2F3BD-1258-40FC-803A-EE7EEC928107}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{C5F2F3BD-1258-40FC-803A-EE7EEC928107}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{C5F2F3BD-1258-40FC-803A-EE7EEC928107}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{C5F2F3BD-1258-40FC-803A-EE7EEC928107}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{C5F2F3BD-1258-40FC-803A-EE7EEC928107}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{C5F2F3BD-1258-40FC-803A-EE7EEC928107}.Release|x86.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
|
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ def _request_json(url: str, token: str, method: str = "GET", body: dict[str, Any
|
|||||||
headers = {
|
headers = {
|
||||||
"Authorization": f"token {token}",
|
"Authorization": f"token {token}",
|
||||||
"Accept": "application/json",
|
"Accept": "application/json",
|
||||||
|
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
||||||
}
|
}
|
||||||
if body is not None:
|
if body is not None:
|
||||||
headers["Content-Type"] = "application/json"
|
headers["Content-Type"] = "application/json"
|
||||||
|
|||||||
Reference in New Issue
Block a user