feat(ui): Blazor WebAssembly 마이그레이션 및 API-First 로그인 구현
This commit is contained in:
+7
-9
@@ -1,16 +1,16 @@
|
|||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using Microsoft.AspNetCore.Components.Authorization;
|
using Microsoft.AspNetCore.Components.Authorization;
|
||||||
using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage;
|
using QuantEngine.Web.Client.Services;
|
||||||
|
|
||||||
namespace QuantEngine.Web.Infrastructure
|
namespace QuantEngine.Web.Client.Infrastructure
|
||||||
{
|
{
|
||||||
public class CustomAuthenticationStateProvider : AuthenticationStateProvider
|
public class CustomAuthenticationStateProvider : AuthenticationStateProvider
|
||||||
{
|
{
|
||||||
private readonly ProtectedLocalStorage _localStorage;
|
private readonly LocalStorageService _localStorage;
|
||||||
private readonly ClaimsPrincipal _anonymous = new ClaimsPrincipal(new ClaimsIdentity());
|
private readonly ClaimsPrincipal _anonymous = new ClaimsPrincipal(new ClaimsIdentity());
|
||||||
private const string StorageKey = "quant_admin_session";
|
private const string StorageKey = "quant_admin_session";
|
||||||
|
|
||||||
public CustomAuthenticationStateProvider(ProtectedLocalStorage localStorage)
|
public CustomAuthenticationStateProvider(LocalStorageService localStorage)
|
||||||
{
|
{
|
||||||
_localStorage = localStorage;
|
_localStorage = localStorage;
|
||||||
}
|
}
|
||||||
@@ -19,11 +19,9 @@ namespace QuantEngine.Web.Infrastructure
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// ProtectedLocalStorage call will throw an exception during pre-rendering
|
var username = await _localStorage.GetAsync<string>(StorageKey);
|
||||||
var result = await _localStorage.GetAsync<string>(StorageKey);
|
if (!string.IsNullOrEmpty(username))
|
||||||
if (result.Success && !string.IsNullOrEmpty(result.Value))
|
|
||||||
{
|
{
|
||||||
var username = result.Value;
|
|
||||||
var identity = new ClaimsIdentity(new[]
|
var identity = new ClaimsIdentity(new[]
|
||||||
{
|
{
|
||||||
new Claim(ClaimTypes.Name, username),
|
new Claim(ClaimTypes.Name, username),
|
||||||
@@ -36,7 +34,7 @@ namespace QuantEngine.Web.Infrastructure
|
|||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
// Return anonymous state during pre-rendering or if storage reading fails
|
// Return anonymous if localStorage isn't ready
|
||||||
}
|
}
|
||||||
|
|
||||||
return new AuthenticationState(_anonymous);
|
return new AuthenticationState(_anonymous);
|
||||||
+14
-19
@@ -1,11 +1,10 @@
|
|||||||
@inherits LayoutComponentBase
|
@inherits LayoutComponentBase
|
||||||
@inject Microsoft.AspNetCore.Hosting.IWebHostEnvironment WebHostEnvironment
|
@inject HttpClient Http
|
||||||
@inject AuthenticationStateProvider AuthStateProvider
|
@inject AuthenticationStateProvider AuthStateProvider
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@using System.IO
|
@using System.Net.Http.Json
|
||||||
@using System.Text.Json
|
|
||||||
@using Microsoft.FluentUI.AspNetCore.Components
|
@using Microsoft.FluentUI.AspNetCore.Components
|
||||||
@using QuantEngine.Web.Infrastructure
|
@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 -->
|
||||||
@@ -77,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
|
||||||
@@ -110,5 +99,11 @@
|
|||||||
await customProvider.MarkUserAsLoggedOutAsync();
|
await customProvider.MarkUserAsLoggedOutAsync();
|
||||||
NavigationManager.NavigateTo("login");
|
NavigationManager.NavigateTo("login");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class VersionInfo
|
||||||
|
{
|
||||||
|
public string? Version { get; set; }
|
||||||
|
public string? Built { get; set; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
@page "/collection"
|
@page "/collection"
|
||||||
@using QuantEngine.Web.Services
|
@using QuantEngine.Web.Client.Services
|
||||||
@inject ApiClient ApiClient
|
@inject ApiClient ApiClient
|
||||||
@inject ILogger<Collection> Logger
|
@inject ILogger<Collection> Logger
|
||||||
|
|
||||||
|
|||||||
+21
-10
@@ -1,6 +1,6 @@
|
|||||||
@page "/"
|
@page "/"
|
||||||
@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 +96,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";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+3
-5
@@ -2,7 +2,7 @@
|
|||||||
@attribute [AllowAnonymous]
|
@attribute [AllowAnonymous]
|
||||||
@inject AuthenticationStateProvider AuthStateProvider
|
@inject AuthenticationStateProvider AuthStateProvider
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject IConfiguration Configuration
|
@inject HttpClient Http
|
||||||
|
|
||||||
<PageTitle>로그인 - QuantEngine</PageTitle>
|
<PageTitle>로그인 - QuantEngine</PageTitle>
|
||||||
|
|
||||||
@@ -281,11 +281,9 @@
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Verify against configurations in appsettings.json
|
var response = await Http.PostAsJsonAsync("api/auth/login", new { Username, Password });
|
||||||
var expectedUser = Configuration["AdminSettings:Username"] ?? "admin";
|
|
||||||
var expectedPass = Configuration["AdminSettings:Password"] ?? "quant123!";
|
|
||||||
|
|
||||||
if (Username == expectedUser && Password == expectedPass)
|
if (response.IsSuccessStatusCode)
|
||||||
{
|
{
|
||||||
var customProvider = (CustomAuthenticationStateProvider)AuthStateProvider;
|
var customProvider = (CustomAuthenticationStateProvider)AuthStateProvider;
|
||||||
await customProvider.MarkUserAsAuthenticatedAsync(Username);
|
await customProvider.MarkUserAsAuthenticatedAsync(Username);
|
||||||
+23
-13
@@ -1,6 +1,6 @@
|
|||||||
@page "/operations"
|
@page "/operations"
|
||||||
@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 +97,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>
|
||||||
+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,7 +1,11 @@
|
|||||||
|
@using QuantEngine.Web.Client
|
||||||
|
@using QuantEngine.Web.Client.Pages
|
||||||
|
@using QuantEngine.Web.Client.Layout
|
||||||
|
|
||||||
<CascadingAuthenticationState>
|
<CascadingAuthenticationState>
|
||||||
<Router AppAssembly="typeof(Program).Assembly" NotFoundPage="typeof(Pages.NotFound)">
|
<Router AppAssembly="typeof(Dashboard).Assembly" NotFoundPage="typeof(NotFound)">
|
||||||
<Found Context="routeData">
|
<Found Context="routeData">
|
||||||
<AuthorizeRouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)">
|
<AuthorizeRouteView RouteData="routeData" DefaultLayout="typeof(MainLayout)">
|
||||||
<NotAuthorized>
|
<NotAuthorized>
|
||||||
<RedirectToLogin />
|
<RedirectToLogin />
|
||||||
</NotAuthorized>
|
</NotAuthorized>
|
||||||
|
|||||||
@@ -1,7 +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 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;
|
||||||
@@ -10,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
|
||||||
@@ -25,10 +26,11 @@ 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
|
// Authentication and Custom State Provider (Shared client components)
|
||||||
builder.Services.AddCascadingAuthenticationState();
|
builder.Services.AddCascadingAuthenticationState();
|
||||||
|
builder.Services.AddScoped<LocalStorageService>();
|
||||||
builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthenticationStateProvider>();
|
builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthenticationStateProvider>();
|
||||||
builder.Services.AddAuthorizationCore();
|
builder.Services.AddAuthorizationCore();
|
||||||
|
|
||||||
@@ -100,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);
|
||||||
@@ -144,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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user