feat(ui): migrate web shell to mudblazor
WBS-9.3 - NULL Policy CI Gate / NULL Policy Validation (push) Failing after 6s
Quant Engine CI/CD Pipeline / validate-core (push) Failing after 11s
Deploy to Production / Build & Deploy to Production (push) Failing after 1m55s
Quant Engine CI/CD Pipeline / validate-ui-and-storage (push) Has been skipped

This commit is contained in:
2026-07-01 13:24:46 +09:00
parent fe8ff44d3f
commit 28e1a8775f
14 changed files with 285 additions and 589 deletions
@@ -0,0 +1,3 @@
@inherits LayoutComponentBase
@Body
@@ -2,74 +2,36 @@
@inject HttpClient Http @inject HttpClient Http
@inject AuthenticationStateProvider AuthStateProvider @inject AuthenticationStateProvider AuthStateProvider
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@using System.Net.Http.Json
@using Microsoft.FluentUI.AspNetCore.Components
@using QuantEngine.Web.Client.Infrastructure
<FluentStack Orientation="Orientation.Vertical" Class="h-100 w-100"> <MudLayout>
<!-- Header --> <MudAppBar Elevation="1" Dense="true">
<FluentHeader> <MudIconButton Icon="@Icons.Material.Filled.Menu" Color="Color.Inherit" Edge="Edge.Start" OnClick="@(() => navOpen = !navOpen)" />
<FluentStack Orientation="Orientation.Horizontal" VerticalAlignment="VerticalAlignment.Center" <MudText Typo="Typo.h6">QuantEngine v@appVersion</MudText>
Style="width: 100%; padding: 8px 16px; gap: 16px;"> <MudSpacer />
<FluentButton OnClick="@(() => navOpen = !navOpen)" <AuthorizeView>
Title="Toggle Navigation" <Authorized>
Style="background: transparent; border: none; cursor: pointer;"> <MudText Typo="Typo.body2">관리자 (@context.User.Identity?.Name)</MudText>
<MudButton Variant="Variant.Outlined" Color="Color.Error" OnClick="HandleLogoutAsync">로그아웃</MudButton>
</FluentButton> </Authorized>
<h1 style="margin: 0; font-size: 20px; font-weight: 600;">QuantEngine v@appVersion</h1> </AuthorizeView>
<AuthorizeView> </MudAppBar>
<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>
</FluentHeader>
<!-- Main Content Area --> <MudDrawer Open="@navOpen" Variant="DrawerVariant.Responsive" Elevation="1">
<FluentStack Orientation="Orientation.Horizontal" Class="flex-1" Style="overflow: hidden;"> <MudNavMenu>
<!-- Navigation Sidebar --> <NavMenu />
@if (navOpen) </MudNavMenu>
{ <div style="padding: 16px; border-top: 1px solid var(--mud-palette-lines-default);">
<nav style="width: 240px; background: var(--neutral-layer-1); border-right: 1px solid var(--neutral-stroke-1); padding: 12px; overflow-y: auto;"> <MudText Typo="Typo.caption">QuantEngine v@appVersion</MudText>
<NavMenu /> <MudText Typo="Typo.caption">배포: @buildTime</MudText>
<div style="margin-top: auto; padding-top: 12px; border-top: 1px solid var(--neutral-stroke-1); margin-top: 12px; font-size: 11px; color: var(--neutral-foreground-3); line-height: 1.5;"> </div>
<div style="font-weight: 500; margin-bottom: 2px;">QuantEngine v@appVersion</div> </MudDrawer>
<div style="font-size: 10px; opacity: 0.85;">배포: @buildTime</div>
</div>
</nav>
}
<!-- Page Content --> <MudMainContent>
<FluentStack Orientation="Orientation.Vertical" Class="flex-1" Style="overflow-y: auto; padding: 24px;"> <MudContainer MaxWidth="MaxWidth.False" Class="pa-4">
@Body @Body
</FluentStack> </MudContainer>
</FluentStack> </MudMainContent>
</FluentStack> </MudLayout>
<div id="blazor-error-ui" data-nosnippet>
<div class="alert alert-danger" role="alert">
<p>An unhandled error has occurred.</p>
<a href="." class="btn btn-primary">Reload</a>
</div>
</div>
<style>
.h-100 {
height: 100%;
}
.w-100 {
width: 100%;
}
.flex-1 {
flex: 1;
display: flex;
}
</style>
@code { @code {
private bool navOpen = true; private bool navOpen = true;
@@ -89,7 +51,6 @@
} }
catch catch
{ {
// Fail-safe default fallback values
} }
} }
@@ -97,7 +58,7 @@
{ {
var customProvider = (CustomAuthenticationStateProvider)AuthStateProvider; var customProvider = (CustomAuthenticationStateProvider)AuthStateProvider;
await customProvider.LogoutFromServerAsync(); await customProvider.LogoutFromServerAsync();
NavigationManager.NavigateTo("login"); NavigationManager.NavigateTo("/login");
} }
private class VersionInfo private class VersionInfo
@@ -106,4 +67,3 @@
public string? Built { get; set; } public string? Built { get; set; }
} }
} }
@@ -1,10 +1,4 @@
@using Microsoft.FluentUI.AspNetCore.Components <MudNavMenu>
<MudNavLink Href="/dashboard" Match="NavLinkMatch.All">Dashboard</MudNavLink>
<FluentNavMenu> <MudNavLink Href="/operations" Match="NavLinkMatch.Prefix">Operations</MudNavLink>
<FluentNavLink Href="/" Match="NavLinkMatch.All"> </MudNavMenu>
Dashboard
</FluentNavLink>
<FluentNavLink Href="/operations" Match="NavLinkMatch.Prefix">
Operations
</FluentNavLink>
</FluentNavMenu>
@@ -6,118 +6,88 @@
<PageTitle>QuantEngine - Collection</PageTitle> <PageTitle>QuantEngine - Collection</PageTitle>
<h1 style="margin: 0 0 8px 0; font-size: 28px; font-weight: 600;">Data Collection</h1> <MudText Typo="Typo.h4" Class="mb-2">Data Collection</MudText>
<p style="margin: 0 0 16px 0; color: var(--neutral-foreground-2); font-size: 14px;"> <MudText Typo="Typo.body2" Class="mb-4">KIS API data collection dashboard. API-first로만 동작합니다.</MudText>
KIS API data collection dashboard. Monitor runs, snapshots, and error trends.
</p>
<!-- Controls --> <MudStack Row="true" Spacing="2" Class="mb-4">
<FluentStack Orientation="Orientation.Horizontal" HorizontalGap="8" Style="margin-bottom: 16px;"> <MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="@StartCollectionAsync" Disabled="@IsProcessing">
<FluentButton Appearance="ButtonAppearance.Primary" OnClick="@StartCollectionAsync" Disabled="@IsProcessing"> @(IsProcessing ? "Running..." : "Start Collection")
@if (IsProcessing) { <span>Running...</span> } else { <span>Start Collection</span> } </MudButton>
</FluentButton> <MudButton Variant="Variant.Outlined" OnClick="@RefreshAsync" Disabled="@IsProcessing">Refresh</MudButton>
<FluentButton Appearance="ButtonAppearance.Default" OnClick="@RefreshAsync" Disabled="@IsProcessing"> </MudStack>
Refresh
</FluentButton>
</FluentStack>
<!-- Loading skeleton -->
@if (IsLoading) @if (IsLoading)
{ {
<FluentStack Orientation="Orientation.Vertical" VerticalGap="16"> <MudProgressLinear Indeterminate="true" Color="Color.Primary" Class="mb-4" />
<FluentSkeleton Width="100%" Height="60px" />
<FluentSkeleton Width="100%" Height="200px" />
</FluentStack>
} }
else if (DashboardState != null) else if (DashboardState != null)
{ {
<!-- Summary Cards --> <MudGrid Spacing="2" Class="mb-4">
<FluentStack Orientation="Orientation.Horizontal" HorizontalGap="16" Wrap="true" Style="margin-bottom: 16px;"> <MudItem xs="12" sm="4">
<FluentCard Style="flex: 1; min-width: 150px;"> <MudPaper Class="pa-4" Elevation="2">
<div style="padding: 16px;"> <MudText Typo="Typo.caption">Last Run</MudText>
<p style="margin: 0 0 8px 0; color: var(--neutral-foreground-2); font-size: 12px; font-weight: 500;">Last Run</p> <MudText Typo="Typo.h6">@(DashboardState.LastRunStatus ?? "N/A")</MudText>
<h3 style="margin: 0; font-size: 18px; font-weight: 600;">@(DashboardState.LastRunStatus ?? "N/A")</h3> <MudText Typo="Typo.body2">@(DashboardState.LastFinishedAt ?? "Not finished")</MudText>
<p style="margin: 8px 0 0 0; color: var(--neutral-foreground-3); font-size: 12px;">@(DashboardState.LastFinishedAt ?? "Not finished")</p> </MudPaper>
</div> </MudItem>
</FluentCard> <MudItem xs="12" sm="4">
<FluentCard Style="flex: 1; min-width: 150px;"> <MudPaper Class="pa-4" Elevation="2">
<div style="padding: 16px;"> <MudText Typo="Typo.caption">Total Snapshots</MudText>
<p style="margin: 0 0 8px 0; color: var(--neutral-foreground-2); font-size: 12px; font-weight: 500;">Total Snapshots</p> <MudText Typo="Typo.h6">@DashboardState.TotalSnapshots</MudText>
<h3 style="margin: 0; font-size: 18px; font-weight: 600;">@DashboardState.TotalSnapshots</h3> </MudPaper>
</div> </MudItem>
</FluentCard> <MudItem xs="12" sm="4">
<FluentCard Style="flex: 1; min-width: 150px;"> <MudPaper Class="pa-4" Elevation="2">
<div style="padding: 16px;"> <MudText Typo="Typo.caption">Total Errors</MudText>
<p style="margin: 0 0 8px 0; color: var(--neutral-foreground-2); font-size: 12px; font-weight: 500;">Total Errors</p> <MudText Typo="Typo.h6">@DashboardState.TotalErrors</MudText>
<h3 style="margin: 0; font-size: 18px; font-weight: 600;">@DashboardState.TotalErrors</h3> </MudPaper>
</div> </MudItem>
</FluentCard> </MudGrid>
</FluentStack>
<!-- Recent Errors -->
@if (DashboardState.RecentErrors.Count > 0) @if (DashboardState.RecentErrors.Count > 0)
{ {
<FluentCard Style="margin-bottom: 16px;"> <MudPaper Class="pa-4 mb-4" Elevation="2">
<div style="padding: 16px;"> <MudText Typo="Typo.h6" Class="mb-3">Recent Errors</MudText>
<h3 style="margin: 0 0 12px 0; font-size: 16px; font-weight: 600;">Recent Errors</h3> <MudTable Items="@DashboardState.RecentErrors" Dense="true" Hover="true">
<table style="width: 100%; border-collapse: collapse;"> <HeaderContent>
<thead style="background: var(--neutral-subtle);"> <MudTh>Source</MudTh>
<tr> <MudTh>Kind</MudTh>
<th style="padding: 8px; text-align: left; border-bottom: 1px solid var(--neutral-divider-rest);">Source</th> <MudTh>Ticker</MudTh>
<th style="padding: 8px; text-align: left; border-bottom: 1px solid var(--neutral-divider-rest);">Kind</th> <MudTh>Message</MudTh>
<th style="padding: 8px; text-align: left; border-bottom: 1px solid var(--neutral-divider-rest);">Ticker</th> </HeaderContent>
<th style="padding: 8px; text-align: left; border-bottom: 1px solid var(--neutral-divider-rest);">Message</th> <RowTemplate>
</tr> <MudTd DataLabel="Source">@context.SourceName</MudTd>
</thead> <MudTd DataLabel="Kind">@context.ErrorKind</MudTd>
<tbody> <MudTd DataLabel="Ticker">@context.Ticker</MudTd>
@foreach (var error in DashboardState.RecentErrors) <MudTd DataLabel="Message">@context.ErrorMessage</MudTd>
{ </RowTemplate>
<tr> </MudTable>
<td style="padding: 8px; border-bottom: 1px solid var(--neutral-stroke-divider-rest);">@error.SourceName</td> </MudPaper>
<td style="padding: 8px; border-bottom: 1px solid var(--neutral-stroke-divider-rest);">@error.ErrorKind</td>
<td style="padding: 8px; border-bottom: 1px solid var(--neutral-stroke-divider-rest);">@error.Ticker</td>
<td style="padding: 8px; border-bottom: 1px solid var(--neutral-stroke-divider-rest);">@error.ErrorMessage</td>
</tr>
}
</tbody>
</table>
</div>
</FluentCard>
} }
<!-- Recent Runs -->
@if (RecentRuns != null && RecentRuns.Count > 0) @if (RecentRuns != null && RecentRuns.Count > 0)
{ {
<FluentCard> <MudPaper Class="pa-4" Elevation="2">
<div style="padding: 16px;"> <MudText Typo="Typo.h6" Class="mb-3">Recent Runs</MudText>
<h3 style="margin: 0 0 12px 0; font-size: 16px; font-weight: 600;">Recent Runs</h3> <MudTable Items="@RecentRuns" Dense="true" Hover="true">
<table style="width: 100%; border-collapse: collapse;"> <HeaderContent>
<thead style="background: var(--neutral-subtle);"> <MudTh>Run ID</MudTh>
<tr> <MudTh>Status</MudTh>
<th style="padding: 8px; text-align: left; border-bottom: 1px solid var(--neutral-divider-rest);">Run ID</th> <MudTh>Started</MudTh>
<th style="padding: 8px; text-align: left; border-bottom: 1px solid var(--neutral-divider-rest);">Status</th> <MudTh>Finished</MudTh>
<th style="padding: 8px; text-align: left; border-bottom: 1px solid var(--neutral-divider-rest);">Started</th> <MudTh>Snapshots</MudTh>
<th style="padding: 8px; text-align: left; border-bottom: 1px solid var(--neutral-divider-rest);">Finished</th> <MudTh>Errors</MudTh>
<th style="padding: 8px; text-align: left; border-bottom: 1px solid var(--neutral-divider-rest);">Snapshots</th> </HeaderContent>
<th style="padding: 8px; text-align: left; border-bottom: 1px solid var(--neutral-divider-rest);">Errors</th> <RowTemplate>
</tr> <MudTd DataLabel="Run ID" Style="font-family: monospace; font-size: 12px;">@context.RunId</MudTd>
</thead> <MudTd DataLabel="Status">@context.Status</MudTd>
<tbody> <MudTd DataLabel="Started">@context.StartedAt</MudTd>
@foreach (var run in RecentRuns) <MudTd DataLabel="Finished">@context.FinishedAt</MudTd>
{ <MudTd DataLabel="Snapshots">@context.TotalSnapshots</MudTd>
<tr> <MudTd DataLabel="Errors">@context.TotalErrors</MudTd>
<td style="padding: 8px; border-bottom: 1px solid var(--neutral-stroke-divider-rest); font-family: monospace; font-size: 12px;">@run.RunId</td> </RowTemplate>
<td style="padding: 8px; border-bottom: 1px solid var(--neutral-stroke-divider-rest);">@run.Status</td> </MudTable>
<td style="padding: 8px; border-bottom: 1px solid var(--neutral-stroke-divider-rest); font-size: 12px;">@run.StartedAt</td> </MudPaper>
<td style="padding: 8px; border-bottom: 1px solid var(--neutral-stroke-divider-rest); font-size: 12px;">@run.FinishedAt</td>
<td style="padding: 8px; border-bottom: 1px solid var(--neutral-stroke-divider-rest);">@run.TotalSnapshots</td>
<td style="padding: 8px; border-bottom: 1px solid var(--neutral-stroke-divider-rest);">@run.TotalErrors</td>
</tr>
}
</tbody>
</table>
</div>
</FluentCard>
} }
} }
@@ -138,7 +108,6 @@ else if (DashboardState != null)
try try
{ {
DashboardState = await ApiClient.GetCollectionStateAsync(); DashboardState = await ApiClient.GetCollectionStateAsync();
var runsResponse = await ApiClient.GetCollectionRunsAsync(10); var runsResponse = await ApiClient.GetCollectionRunsAsync(10);
RecentRuns = runsResponse?.Runs ?? new(); RecentRuns = runsResponse?.Runs ?? new();
} }
@@ -1,89 +1,84 @@
@page "/" @page "/dashboard"
@attribute [Authorize] @attribute [Authorize]
@using QuantEngine.Core.Infrastructure @using QuantEngine.Core.Infrastructure
@inject HttpClient Http @inject HttpClient Http
<PageTitle>Quant Engine - Dashboard</PageTitle> <PageTitle>Quant Engine - Dashboard</PageTitle>
<h1 style="margin: 0 0 8px 0; font-size: 28px; font-weight: 600;">Quant Engine</h1> <MudText Typo="Typo.h4" Class="mb-2">Quant Engine</MudText>
<p style="margin: 0 0 16px 0; color: var(--neutral-foreground-2); font-size: 14px;"> <MudText Typo="Typo.body2" Class="mb-4">운영 진입점입니다. 로그인 후 현재 스냅샷 상태와 리포트 경로만 표시합니다.</MudText>
루트 화면은 운영 진입점입니다. 가짜 성과 수치 없이 현재 스냅샷 상태와 리포트 경로만 보여줍니다.
</p>
<!-- Top 3 Cards --> <MudGrid Spacing="2" Class="mb-4">
<FluentStack Orientation="Orientation.Horizontal" HorizontalGap="16" Wrap="true" Style="margin-bottom: 16px;"> <MudItem xs="12" sm="4">
<FluentCard Style="flex: 1; min-width: 200px;"> <MudPaper Class="pa-4" Elevation="2">
<div style="padding: 16px;"> <MudText Typo="Typo.caption">Operational Report</MudText>
<p style="margin: 0 0 8px 0; color: var(--neutral-foreground-2); font-size: 12px; font-weight: 500;">Operational Report</p> <MudText Typo="Typo.h6">@ReportStateLabel</MudText>
<h3 style="margin: 0; font-size: 20px; font-weight: 600;">@ReportStateLabel</h3> <MudText Typo="Typo.body2">@ReportPath</MudText>
<p style="margin: 8px 0 0 0; color: var(--neutral-foreground-3); font-size: 12px;">@ReportPath</p> </MudPaper>
</div> </MudItem>
</FluentCard> <MudItem xs="12" sm="4">
<FluentCard Style="flex: 1; min-width: 200px;"> <MudPaper Class="pa-4" Elevation="2">
<div style="padding: 16px;"> <MudText Typo="Typo.caption">Sections</MudText>
<p style="margin: 0 0 8px 0; color: var(--neutral-foreground-2); font-size: 12px; font-weight: 500;">Sections</p> <MudText Typo="Typo.h6">@SectionCountLabel</MudText>
<h3 style="margin: 0; font-size: 20px; font-weight: 600;">@SectionCountLabel</h3> <MudText Typo="Typo.body2">Temp/operational_report.json</MudText>
<p style="margin: 8px 0 0 0; color: var(--neutral-foreground-3); font-size: 12px;">Temp/operational_report.json</p> </MudPaper>
</div> </MudItem>
</FluentCard> <MudItem xs="12" sm="4">
<FluentCard Style="flex: 1; min-width: 200px;"> <MudPaper Class="pa-4" Elevation="2">
<div style="padding: 16px;"> <MudText Typo="Typo.caption">Primary Route</MudText>
<p style="margin: 0 0 8px 0; color: var(--neutral-foreground-2); font-size: 12px; font-weight: 500;">Primary Route</p> <MudButton Variant="Variant.Filled" Color="Color.Primary" Href="/operations" Class="mt-2">Open Operations</MudButton>
<FluentButton Appearance="ButtonAppearance.Primary" Href="/operations" Style="margin-top: 8px;"> </MudPaper>
Open Operations </MudItem>
</FluentButton> </MudGrid>
</div>
</FluentCard>
</FluentStack>
<!-- Current State & Routing Notes --> <MudGrid Spacing="2" Class="mb-4">
<FluentStack Orientation="Orientation.Horizontal" HorizontalGap="16" Wrap="true" Style="margin-bottom: 16px;"> <MudItem xs="12" md="8">
<FluentCard Style="flex: 2; min-width: 300px;"> <MudPaper Class="pa-4" Elevation="2">
<div style="padding: 16px;"> <MudText Typo="Typo.h6" Class="mb-3">Current State</MudText>
<h3 style="margin: 0 0 12px 0; font-size: 16px; font-weight: 600;">Current State</h3> <MudStack Spacing="1">
<FluentStack Orientation="Orientation.Vertical" VerticalGap="8"> <MudText Typo="Typo.body2">Status: <MudChip T="string" Color="@(ReportChipLabel == "READY" ? Color.Success : Color.Warning)" Variant="Variant.Filled">@ReportChipLabel</MudChip></MudText>
<p style="margin: 0; font-size: 14px;"><strong>Status:</strong> <FluentBadge Appearance="BadgeAppearance.Filled">@ReportChipLabel</FluentBadge></p> <MudText Typo="Typo.body2">Generated: @GeneratedAtLabel</MudText>
<p style="margin: 0; font-size: 14px;"><strong>Generated:</strong> @GeneratedAtLabel</p> <MudText Typo="Typo.body2">Source: @SourceLabel</MudText>
<p style="margin: 0; font-size: 14px;"><strong>Source:</strong> @SourceLabel</p> <MudText Typo="Typo.body2">Decision feed: @DecisionFeedLabel</MudText>
<p style="margin: 0; font-size: 14px;"><strong>Decision feed:</strong> @DecisionFeedLabel</p> <MudText Typo="Typo.body2">Factor feed: @FactorFeedLabel</MudText>
<p style="margin: 0; font-size: 14px;"><strong>Factor feed:</strong> @FactorFeedLabel</p> <MudText Typo="Typo.body2">Raw feed: @RawFeedLabel</MudText>
<p style="margin: 0; font-size: 14px;"><strong>Raw feed:</strong> @RawFeedLabel</p> </MudStack>
</FluentStack> </MudPaper>
</div> </MudItem>
</FluentCard> <MudItem xs="12" md="4">
<MudPaper Class="pa-4" Elevation="2">
<FluentCard Style="flex: 1; min-width: 250px;"> <MudText Typo="Typo.h6" Class="mb-3">Routing Notes</MudText>
<div style="padding: 16px;"> <ul style="margin: 0; padding-left: 18px;">
<h3 style="margin: 0 0 12px 0; font-size: 16px; font-weight: 600;">Routing Notes</h3>
<ul style="margin: 0; padding-left: 16px; font-size: 14px;">
<li>운영 데이터는 snapshot 우선입니다.</li> <li>운영 데이터는 snapshot 우선입니다.</li>
<li>Excel/GAS 의존 문구는 운영 경로에서 제거 대상입니다.</li> <li>Excel/GAS 의존 문구는 제거 대상입니다.</li>
<li>숫자는 provenance 없으면 표시하지 않습니다.</li> <li>숫자는 provenance 없으면 표시하지 않습니다.</li>
</ul> </ul>
</div> </MudPaper>
</FluentCard> </MudItem>
</FluentStack> </MudGrid>
<!-- Coverage Summary --> <MudPaper Class="pa-4" Elevation="2">
<FluentCard> <MudText Typo="Typo.h6" Class="mb-3">Coverage Summary</MudText>
<div style="padding: 16px;"> @if (Sections.Count == 0)
<h3 style="margin: 0 0 12px 0; font-size: 16px; font-weight: 600;">Coverage Summary</h3> {
@if (Sections.Count == 0) <MudAlert Severity="Severity.Warning">DATA_MISSING: operational_report.json이 비어 있거나 아직 생성되지 않았습니다.</MudAlert>
{ }
<div style="padding: 12px; background: var(--warning-background-1); border: 1px solid var(--warning-stroke-1); border-radius: 4px; color: var(--warning-foreground-1); font-size: 14px;"> else
DATA_MISSING: operational_report.json이 비어 있거나 아직 생성되지 않았습니다. {
</div> <MudTable Items="@Sections" Dense="true" Hover="true">
} <HeaderContent>
else <MudTh>Name</MudTh>
{ <MudTh>Title</MudTh>
<FluentDataGrid Items="@Sections.AsQueryable()"> <MudTh>Preview</MudTh>
<PropertyColumn Property="@(x => x.Name)" Title="Name" /> </HeaderContent>
<PropertyColumn Property="@(x => x.Title)" Title="Title" /> <RowTemplate>
<PropertyColumn Property="@(x => x.Preview)" Title="Preview" /> <MudTd DataLabel="Name">@context.Name</MudTd>
</FluentDataGrid> <MudTd DataLabel="Title">@context.Title</MudTd>
} <MudTd DataLabel="Preview">@context.Preview</MudTd>
</div> </RowTemplate>
</FluentCard> </MudTable>
}
</MudPaper>
@code { @code {
private readonly List<OperationalReportSection> Sections = new(); private readonly List<OperationalReportSection> Sections = new();
@@ -1,264 +1,54 @@
@page "/login" @page "/login"
@attribute [AllowAnonymous] @attribute [AllowAnonymous]
@layout AuthLayout
@inject AuthenticationStateProvider AuthStateProvider @inject AuthenticationStateProvider AuthStateProvider
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject HttpClient Http @inject HttpClient Http
<PageTitle>로그인 - QuantEngine</PageTitle> <PageTitle>로그인 - QuantEngine</PageTitle>
<div class="auth-container"> <MudContainer MaxWidth="MaxWidth.False" Class="login-shell">
<div class="auth-card"> <MudPaper Class="login-card pa-8" Elevation="10">
<div class="brand-section"> <MudStack AlignItems="AlignItems.Center" Spacing="2" Class="mb-6">
<img src="images/quant_engine_logo.jpg" alt="QuantEngine Logo" class="brand-logo" /> <MudAvatar Size="Size.Large" Color="Color.Primary">Q</MudAvatar>
<h1 class="brand-title">QuantEngine</h1> <MudText Typo="Typo.h4">QuantEngine</MudText>
<p class="brand-subtitle">은퇴자산포트폴리오 투자 관리 시스템</p> <MudText Typo="Typo.body2" Align="Align.Center">은퇴자산포트폴리오 투자 관리 시스템</MudText>
</div> </MudStack>
<form @onsubmit="HandleLoginAsync" @onsubmit:preventDefault="true" class="auth-form"> <MudStack Spacing="2">
<div class="form-group"> <MudTextField Label="관리자 아이디" @bind-Value="Username" Variant="Variant.Outlined" Immediate="true" AutoFocus="true" />
<label for="username">관리자 아이디</label> <MudTextField Label="비밀번호" @bind-Value="Password" Variant="Variant.Outlined" InputType="InputType.Password" Immediate="true" />
<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)) @if (!string.IsNullOrEmpty(ErrorMessage))
{ {
<div class="error-message"> <MudAlert Severity="Severity.Error">@ErrorMessage</MudAlert>
<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"> <MudButton Variant="Variant.Filled" Color="Color.Primary" FullWidth="true" Disabled="@IsSubmitting" OnClick="HandleLoginAsync">
@if (IsSubmitting) @(IsSubmitting ? "인증 중..." : "로그인")
{ </MudButton>
<span class="spinner"></span> </MudStack>
<span>인증 중...</span> </MudPaper>
} </MudContainer>
else
{
<span>로그인</span>
}
</button>
</form>
</div>
</div>
<style> <style>
.auth-container { .login-shell {
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh; 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; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
gap: 10px; background:
transition: all 0.3s ease; radial-gradient(circle at top left, rgba(0, 242, 254, 0.08), transparent 30%),
box-shadow: 0 4px 15px rgba(0, 242, 254, 0.2); radial-gradient(circle at bottom right, rgba(79, 172, 254, 0.1), transparent 35%),
linear-gradient(135deg, #090a15 0%, #12142d 100%);
} }
.btn-submit:hover:not(:disabled) { .login-card {
transform: translateY(-2px); width: min(480px, calc(100vw - 32px));
box-shadow: 0 6px 20px rgba(0, 242, 254, 0.35); border-radius: 20px;
} background: rgba(255, 255, 255, 0.04);
backdrop-filter: blur(24px);
.btn-submit:active:not(:disabled) { color: white;
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> </style>
@@ -291,7 +81,6 @@
try try
{ {
var response = await Http.PostAsJsonAsync("api/auth/login", new { Username, Password }); var response = await Http.PostAsJsonAsync("api/auth/login", new { Username, Password });
if (response.IsSuccessStatusCode) if (response.IsSuccessStatusCode)
{ {
var auth = await response.Content.ReadFromJsonAsync<LoginResponse>(); var auth = await response.Content.ReadFromJsonAsync<LoginResponse>();
@@ -303,9 +92,7 @@
var customProvider = (CustomAuthenticationStateProvider)AuthStateProvider; var customProvider = (CustomAuthenticationStateProvider)AuthStateProvider;
await customProvider.MarkUserAsAuthenticatedAsync(auth.Username ?? Username, auth.AccessToken, auth.Role ?? "Admin"); await customProvider.MarkUserAsAuthenticatedAsync(auth.Username ?? Username, auth.AccessToken, auth.Role ?? "Admin");
NavigationManager.NavigateTo("/dashboard");
// Redirect back to home dashboard
NavigationManager.NavigateTo("");
} }
else else
{ {
@@ -3,87 +3,82 @@
@using QuantEngine.Core.Infrastructure @using QuantEngine.Core.Infrastructure
@inject HttpClient Http @inject HttpClient Http
<PageTitle>Quant Engine - Operations</PageTitle> <PageTitle>QuantEngine - Operations</PageTitle>
<h1 style="margin: 0 0 8px 0; font-size: 28px; font-weight: 600;">Operational Report</h1> <MudText Typo="Typo.h4" Class="mb-2">Operational Report</MudText>
<p style="margin: 0 0 16px 0; color: var(--neutral-foreground-2); font-size: 14px;"> <MudText Typo="Typo.body2" Class="mb-4">Temp/operational_report.json만 읽는 운영 고정 화면입니다.</MudText>
이 페이지는 `Temp/operational_report.json`만 읽습니다. DB 연결과 무관하게 동일한 결과를 보여주는 운영 고정 화면입니다.
</p>
<!-- Metadata Cards --> <MudGrid Spacing="2" Class="mb-4">
<FluentStack Orientation="Orientation.Horizontal" HorizontalGap="16" Wrap="true" Style="margin-bottom: 16px;"> <MudItem xs="12" sm="3">
<FluentCard Style="flex: 1; min-width: 150px;"> <MudPaper Class="pa-4" Elevation="2">
<div style="padding: 16px;"> <MudText Typo="Typo.caption">Schema</MudText>
<p style="margin: 0 0 8px 0; color: var(--neutral-foreground-2); font-size: 12px; font-weight: 500;">Schema</p> <MudText Typo="Typo.h6">@SchemaVersion</MudText>
<h3 style="margin: 0; font-size: 18px; font-weight: 600;">@SchemaVersion</h3> </MudPaper>
</div> </MudItem>
</FluentCard> <MudItem xs="12" sm="3">
<FluentCard Style="flex: 1; min-width: 150px;"> <MudPaper Class="pa-4" Elevation="2">
<div style="padding: 16px;"> <MudText Typo="Typo.caption">Sections</MudText>
<p style="margin: 0 0 8px 0; color: var(--neutral-foreground-2); font-size: 12px; font-weight: 500;">Sections</p> <MudText Typo="Typo.h6">@SectionCountLabel</MudText>
<h3 style="margin: 0; font-size: 18px; font-weight: 600;">@SectionCountLabel</h3> </MudPaper>
</div> </MudItem>
</FluentCard> <MudItem xs="12" sm="3">
<FluentCard Style="flex: 1; min-width: 150px;"> <MudPaper Class="pa-4" Elevation="2">
<div style="padding: 16px;"> <MudText Typo="Typo.caption">Source</MudText>
<p style="margin: 0 0 8px 0; color: var(--neutral-foreground-2); font-size: 12px; font-weight: 500;">Source</p> <MudText Typo="Typo.h6">@SourceJson</MudText>
<h3 style="margin: 0; font-size: 18px; font-weight: 600;">@SourceJson</h3> </MudPaper>
</div> </MudItem>
</FluentCard> <MudItem xs="12" sm="3">
<FluentCard Style="flex: 1; min-width: 150px;"> <MudPaper Class="pa-4" Elevation="2">
<div style="padding: 16px;"> <MudText Typo="Typo.caption">Generated</MudText>
<p style="margin: 0 0 8px 0; color: var(--neutral-foreground-2); font-size: 12px; font-weight: 500;">Generated</p> <MudText Typo="Typo.h6">@GeneratedAt</MudText>
<h3 style="margin: 0; font-size: 18px; font-weight: 600;">@GeneratedAt</h3> </MudPaper>
</div> </MudItem>
</FluentCard> </MudGrid>
</FluentStack>
<!-- Highlight Sections Grid --> <MudGrid Spacing="2" Class="mb-4">
<FluentStack Orientation="Orientation.Horizontal" HorizontalGap="16" Wrap="true" Style="margin-bottom: 16px;">
@foreach (var section in HighlightSections) @foreach (var section in HighlightSections)
{ {
<FluentCard Style="flex: 1; min-width: 200px;"> <MudItem xs="12" sm="6" md="3" @key="section.Name">
<div style="padding: 16px;"> <MudPaper Class="pa-4" Elevation="2">
<p style="margin: 0 0 4px 0; color: var(--neutral-foreground-2); font-size: 12px;">@(section.Name)</p> <MudText Typo="Typo.caption">@(section.Name)</MudText>
<h3 style="margin: 4px 0; font-size: 16px; font-weight: 600;">@(section.Title)</h3> <MudText Typo="Typo.h6">@(section.Title)</MudText>
<p style="margin: 8px 0 0 0; color: var(--neutral-foreground-3); font-size: 12px;">@(section.Preview)</p> <MudText Typo="Typo.body2">@(section.Preview)</MudText>
</div> </MudPaper>
</FluentCard> </MudItem>
} }
</FluentStack> </MudGrid>
<!-- Report Health --> <MudPaper Class="pa-4 mb-4" Elevation="2">
<FluentCard Style="margin-bottom: 16px;"> <MudText Typo="Typo.h6" Class="mb-3">Report Health</MudText>
<div style="padding: 16px;"> <MudStack Spacing="1">
<h3 style="margin: 0 0 12px 0; font-size: 16px; font-weight: 600;">Report Health</h3> <MudText Typo="Typo.body2">Status: <MudChip T="string" Color="@(HealthLabel == "PASS" ? Color.Success : Color.Warning)" Variant="Variant.Filled">@HealthLabel</MudChip></MudText>
<FluentStack Orientation="Orientation.Vertical" VerticalGap="8"> <MudText Typo="Typo.body2">Path: @ReportPath</MudText>
<p style="margin: 0; font-size: 14px;"><strong>Status:</strong> <FluentBadge Appearance="BadgeAppearance.Filled">@HealthLabel</FluentBadge></p> <MudText Typo="Typo.body2">Sections rendered: @RenderedSectionCountLabel</MudText>
<p style="margin: 0; font-size: 14px;"><strong>Path:</strong> @ReportPath</p> </MudStack>
<p style="margin: 0; font-size: 14px;"><strong>Sections rendered:</strong> @RenderedSectionCountLabel</p> </MudPaper>
</FluentStack>
</div>
</FluentCard>
<!-- Sections Table --> <MudPaper Class="pa-4" Elevation="2">
<FluentCard> <MudText Typo="Typo.h6" Class="mb-3">Sections</MudText>
<div style="padding: 16px;"> @if (Sections.Count == 0)
<h3 style="margin: 0 0 12px 0; font-size: 16px; font-weight: 600;">Sections</h3> {
@if (Sections.Count == 0) <MudAlert Severity="Severity.Warning">DATA_MISSING: operational_report.json에 표시할 섹션이 없습니다.</MudAlert>
{ }
<div style="padding: 12px; background: var(--warning-background-1); border: 1px solid var(--warning-stroke-1); border-radius: 4px; color: var(--warning-foreground-1); font-size: 14px;"> else
DATA_MISSING: operational_report.json에 표시할 섹션이 없습니다. {
</div> <MudTable Items="@Sections" Dense="true" Hover="true">
} <HeaderContent>
else <MudTh>Name</MudTh>
{ <MudTh>Title</MudTh>
<FluentDataGrid Items="@Sections.AsQueryable()"> <MudTh>Preview</MudTh>
<PropertyColumn Property="@(x => x.Name)" Title="Name" /> </HeaderContent>
<PropertyColumn Property="@(x => x.Title)" Title="Title" /> <RowTemplate>
<PropertyColumn Property="@(x => x.Preview)" Title="Preview" /> <MudTd DataLabel="Name">@context.Name</MudTd>
</FluentDataGrid> <MudTd DataLabel="Title">@context.Title</MudTd>
} <MudTd DataLabel="Preview">@context.Preview</MudTd>
</div> </RowTemplate>
</FluentCard> </MudTable>
}
</MudPaper>
@code { @code {
private readonly List<OperationalReportSection> Sections = new(); private readonly List<OperationalReportSection> Sections = new();
@@ -1,14 +1,10 @@
using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.FluentUI.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Authorization; using Microsoft.AspNetCore.Components.Authorization;
using QuantEngine.Web.Client.Services; using QuantEngine.Web.Client.Services;
using QuantEngine.Web.Client.Infrastructure; using QuantEngine.Web.Client.Infrastructure;
var builder = WebAssemblyHostBuilder.CreateDefault(args); var builder = WebAssemblyHostBuilder.CreateDefault(args);
// Register Fluent UI
builder.Services.AddFluentUIComponents();
// Register LocalStorage for cross-platform session persistence // Register LocalStorage for cross-platform session persistence
builder.Services.AddScoped<LocalStorageService>(); builder.Services.AddScoped<LocalStorageService>();
@@ -16,8 +16,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.0-preview.2.25120.18" /> <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.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="MudBlazor" Version="8.6.0" />
<PackageReference Include="Microsoft.FluentUI.AspNetCore.Components.Icons" Version="5.0.0-rc.4-26177.1" />
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -6,8 +6,7 @@
@using static Microsoft.AspNetCore.Components.Web.RenderMode @using static Microsoft.AspNetCore.Components.Web.RenderMode
@using Microsoft.AspNetCore.Components.Web.Virtualization @using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop @using Microsoft.JSInterop
@using Microsoft.FluentUI.AspNetCore.Components @using MudBlazor
@using Microsoft.FluentUI.AspNetCore.Components.Icons
@using QuantEngine.Web.Client @using QuantEngine.Web.Client
@using QuantEngine.Web.Client.Pages @using QuantEngine.Web.Client.Pages
@using QuantEngine.Web.Client.Layout @using QuantEngine.Web.Client.Layout
@@ -1,14 +1,13 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="ko">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<base href="/" /> <base href="/" />
<ResourcePreloader /> <ResourcePreloader />
<!-- Fluent UI CSS -->
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" rel="stylesheet" /> <link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" rel="stylesheet" />
<link href="_content/Microsoft.FluentUI.AspNetCore.Components/css/fluent-components.css" rel="stylesheet" /> <link href="_content/MudBlazor/MudBlazor.min.css" rel="stylesheet" />
<link rel="stylesheet" href="@Assets["app.css"]" /> <link rel="stylesheet" href="@Assets["app.css"]" />
<link rel="stylesheet" href="@Assets["QuantEngine.Web.styles.css"]" /> <link rel="stylesheet" href="@Assets["QuantEngine.Web.styles.css"]" />
<ImportMap /> <ImportMap />
@@ -18,13 +17,14 @@
</head> </head>
<body> <body>
<FluentDesignSystemProvider> <MudThemeProvider>
<MudDialogProvider />
<MudSnackbarProvider />
<Routes @rendermode="InteractiveWebAssembly" /> <Routes @rendermode="InteractiveWebAssembly" />
<ReconnectModal /> <ReconnectModal />
</FluentDesignSystemProvider> </MudThemeProvider>
<!-- Fluent UI JS --> <script src="_content/MudBlazor/MudBlazor.min.js"></script>
<script src="_content/Microsoft.FluentUI.AspNetCore.Components/js/fluent-components.js"></script>
<script src="@Assets["_framework/blazor.web.js"]"></script> <script src="@Assets["_framework/blazor.web.js"]"></script>
</body> </body>
@@ -6,8 +6,7 @@
@using static Microsoft.AspNetCore.Components.Web.RenderMode @using static Microsoft.AspNetCore.Components.Web.RenderMode
@using Microsoft.AspNetCore.Components.Web.Virtualization @using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop @using Microsoft.JSInterop
@using Microsoft.FluentUI.AspNetCore.Components @using MudBlazor
@using Microsoft.FluentUI.AspNetCore.Components.Icons
@using QuantEngine.Web @using QuantEngine.Web
@using QuantEngine.Web.Components @using QuantEngine.Web.Components
@using QuantEngine.Web.Components.Layout @using QuantEngine.Web.Components.Layout
+4 -3
View File
@@ -8,7 +8,6 @@ using QuantEngine.Core.Interfaces;
using QuantEngine.Application.Services; using QuantEngine.Application.Services;
using System.Text.Json; using System.Text.Json;
using static QuantEngine.Application.Services.DataCollectionService; using static QuantEngine.Application.Services.DataCollectionService;
using Microsoft.FluentUI.AspNetCore.Components;
using Serilog; using Serilog;
using QuantEngine.Web.Client.Infrastructure; using QuantEngine.Web.Client.Infrastructure;
using QuantEngine.Web.Client.Services; using QuantEngine.Web.Client.Services;
@@ -19,6 +18,7 @@ using QuantEngine.Core.Models;
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication;
using System.Text.Encodings.Web; using System.Text.Encodings.Web;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using MudBlazor.Services;
// Serilog Configuration with Telegram Sink // Serilog Configuration with Telegram Sink
Log.Logger = new LoggerConfiguration() Log.Logger = new LoggerConfiguration()
@@ -43,8 +43,7 @@ builder.Services.AddScoped<LocalStorageService>();
builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthenticationStateProvider>(); builder.Services.AddScoped<AuthenticationStateProvider, CustomAuthenticationStateProvider>();
builder.Services.AddAuthorizationCore(); builder.Services.AddAuthorizationCore();
// Fluent UI Services builder.Services.AddMudServices();
builder.Services.AddFluentUIComponents();
// PostgreSQL Dapper Setup // PostgreSQL Dapper Setup
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") var connectionString = builder.Configuration.GetConnectionString("DefaultConnection")
@@ -112,6 +111,8 @@ app.UseAuthorization();
app.MapStaticAssets(); app.MapStaticAssets();
app.MapGet("/", () => Results.Redirect("/login"));
// Collection API Endpoints (must be before MapRazorComponents) // Collection API Endpoints (must be before MapRazorComponents)
app.MapCollectionEndpoints(); app.MapCollectionEndpoints();
@@ -8,8 +8,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.FluentUI.AspNetCore.Components" Version="5.0.0-rc.4-26177.1" /> <PackageReference Include="MudBlazor" Version="8.6.0" />
<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" /> <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="10.0.0-preview.2.25120.18" />
</ItemGroup> </ItemGroup>