WIP: frontend refactor
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
namespace DiunaBI.Application.DTOModels.Common;
|
||||
|
||||
public class PagedResult<T>
|
||||
{
|
||||
public List<T> Items { get; set; } = new();
|
||||
public int TotalCount { get; set; }
|
||||
public int PageSize { get; set; }
|
||||
public int Page { get; set; }
|
||||
public int TotalPages => (int)Math.Ceiling(TotalCount / (double)PageSize);
|
||||
public bool HasPreviousPage => Page > 1;
|
||||
public bool HasNextPage => Page < TotalPages;
|
||||
}
|
||||
37
src/Backend/DiunaBI.Application/DTOModels/LayerDto.cs
Normal file
37
src/Backend/DiunaBI.Application/DTOModels/LayerDto.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
namespace DiunaBI.Application.DTOModels;
|
||||
|
||||
public class LayerDto
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public int Number { get; set; }
|
||||
public string? Name { get; set; }
|
||||
public LayerType Type { get; set; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public DateTime ModifiedAt { get; set; }
|
||||
public Guid CreatedById { get; set; }
|
||||
public Guid ModifiedById { get; set; }
|
||||
public bool IsDeleted { get; set; }
|
||||
public bool IsCancelled { get; set; }
|
||||
public Guid? ParentId { get; set; }
|
||||
|
||||
// Navigation properties
|
||||
public List<RecordDto>? Records { get; set; }
|
||||
public UserDto? CreatedBy { get; set; }
|
||||
public UserDto? ModifiedBy { get; set; }
|
||||
}
|
||||
|
||||
public enum LayerType
|
||||
{
|
||||
Import,
|
||||
Processed,
|
||||
Administration,
|
||||
Dictionary
|
||||
}
|
||||
|
||||
public class LayerFilterRequest
|
||||
{
|
||||
public string? Search { get; set; }
|
||||
public LayerType? Type { get; set; }
|
||||
public int Page { get; set; } = 1;
|
||||
public int PageSize { get; set; } = 50;
|
||||
}
|
||||
50
src/Backend/DiunaBI.Application/DTOModels/RecordDto.cs
Normal file
50
src/Backend/DiunaBI.Application/DTOModels/RecordDto.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
namespace DiunaBI.Application.DTOModels;
|
||||
|
||||
public class RecordDto
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string? Code { get; set; }
|
||||
|
||||
public double? Value1 { get; set; }
|
||||
public double? Value2 { get; set; }
|
||||
public double? Value3 { get; set; }
|
||||
public double? Value4 { get; set; }
|
||||
public double? Value5 { get; set; }
|
||||
public double? Value6 { get; set; }
|
||||
public double? Value7 { get; set; }
|
||||
public double? Value8 { get; set; }
|
||||
public double? Value9 { get; set; }
|
||||
public double? Value10 { get; set; }
|
||||
public double? Value11 { get; set; }
|
||||
public double? Value12 { get; set; }
|
||||
public double? Value13 { get; set; }
|
||||
public double? Value14 { get; set; }
|
||||
public double? Value15 { get; set; }
|
||||
public double? Value16 { get; set; }
|
||||
public double? Value17 { get; set; }
|
||||
public double? Value18 { get; set; }
|
||||
public double? Value19 { get; set; }
|
||||
public double? Value20 { get; set; }
|
||||
public double? Value21 { get; set; }
|
||||
public double? Value22 { get; set; }
|
||||
public double? Value23 { get; set; }
|
||||
public double? Value24 { get; set; }
|
||||
public double? Value25 { get; set; }
|
||||
public double? Value26 { get; set; }
|
||||
public double? Value27 { get; set; }
|
||||
public double? Value28 { get; set; }
|
||||
public double? Value29 { get; set; }
|
||||
public double? Value30 { get; set; }
|
||||
public double? Value31 { get; set; }
|
||||
public double? Value32 { get; set; }
|
||||
|
||||
public string? Desc1 { get; set; }
|
||||
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public DateTime ModifiedAt { get; set; }
|
||||
public bool IsDeleted { get; set; }
|
||||
|
||||
public Guid CreatedById { get; set; }
|
||||
public Guid ModifiedById { get; set; }
|
||||
public Guid LayerId { get; set; }
|
||||
}
|
||||
8
src/Backend/DiunaBI.Application/DTOModels/UserDto.cs
Normal file
8
src/Backend/DiunaBI.Application/DTOModels/UserDto.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace DiunaBI.Application.DTOModels;
|
||||
|
||||
public class UserDto
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
public string? Username { get; set; }
|
||||
public string? Email { get; set; }
|
||||
}
|
||||
42
src/Backend/DiunaBI.UI.Shared/Components/AuthGuard.razor
Normal file
42
src/Backend/DiunaBI.UI.Shared/Components/AuthGuard.razor
Normal file
@@ -0,0 +1,42 @@
|
||||
@using DiunaBI.UI.Shared.Services
|
||||
@inject AuthService AuthService
|
||||
@inject NavigationManager Navigation
|
||||
|
||||
@if (_isLoading)
|
||||
{
|
||||
<div class="d-flex justify-center align-center" style="height: 100vh;">
|
||||
<MudProgressCircular Indeterminate="true" Size="Size.Large" />
|
||||
</div>
|
||||
}
|
||||
else if (_isAuthenticated)
|
||||
{
|
||||
@ChildContent
|
||||
}
|
||||
|
||||
@code {
|
||||
[Parameter] public RenderFragment? ChildContent { get; set; }
|
||||
|
||||
private bool _isLoading = true;
|
||||
private bool _isAuthenticated = false;
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
Console.WriteLine("AuthGuard: Checking authentication...");
|
||||
|
||||
_isAuthenticated = await AuthService.CheckAuthenticationAsync();
|
||||
_isLoading = false;
|
||||
|
||||
Console.WriteLine($"AuthGuard: isAuthenticated={_isAuthenticated}");
|
||||
|
||||
if (!_isAuthenticated)
|
||||
{
|
||||
Console.WriteLine("AuthGuard: Redirecting to /login");
|
||||
Navigation.NavigateTo("/login", replace: true);
|
||||
}
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
5
src/Backend/DiunaBI.UI.Shared/Components/Dashboard.razor
Normal file
5
src/Backend/DiunaBI.UI.Shared/Components/Dashboard.razor
Normal file
@@ -0,0 +1,5 @@
|
||||
@page "/dashboard"
|
||||
@using MudBlazor
|
||||
|
||||
<MudText Typo="Typo.h4">Dashboard</MudText>
|
||||
<p>Tutaj znajdzie się panel ogólny aplikacji</p>
|
||||
15
src/Backend/DiunaBI.UI.Shared/Components/Index.razor
Normal file
15
src/Backend/DiunaBI.UI.Shared/Components/Index.razor
Normal file
@@ -0,0 +1,15 @@
|
||||
@page "/"
|
||||
@inject NavigationManager Navigation
|
||||
|
||||
@code
|
||||
{
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
Navigation.NavigateTo("/dashboard");
|
||||
}
|
||||
|
||||
await base.OnAfterRenderAsync(firstRender);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
@using MudBlazor.Internal
|
||||
<MudText Typo="Typo.h4" Class="mb-4">Lista Produktów</MudText>
|
||||
|
||||
<MudExpansionPanels Class="mb-4">
|
||||
<MudExpansionPanel Icon="@Icons.Material.Filled.FilterList"
|
||||
Text="Filtry"
|
||||
Expanded="true">
|
||||
<MudGrid>
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudTextField @bind-Value="filterRequest.Search"
|
||||
Label="Szukaj"
|
||||
Placeholder="Nazwa, Kod, EAN..."
|
||||
Immediate="true"
|
||||
DebounceInterval="500"
|
||||
OnDebounceIntervalElapsed="SearchProducts"
|
||||
Clearable="true"/>
|
||||
|
||||
</MudItem>
|
||||
|
||||
<MudItem xs="12" sm="6" md="4">
|
||||
<MudButton Variant="Variant.Outlined"
|
||||
OnClick="ClearFilters"
|
||||
StartIcon="Icons.Material.Filled.Clear">
|
||||
Wyczyść filtry
|
||||
</MudButton>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
</MudExpansionPanel>
|
||||
</MudExpansionPanels>
|
||||
|
||||
<MudDivider Class="my-4"></MudDivider>
|
||||
|
||||
<MudTable Items="layers.Items"
|
||||
Dense="true"
|
||||
Hover="true"
|
||||
Loading="isLoading"
|
||||
LoadingProgressColor="Color.Info">
|
||||
<HeaderContent>
|
||||
<MudTh>Nazwa</MudTh>
|
||||
<MudTh>Kod</MudTh>
|
||||
<MudTh>EAN</MudTh>
|
||||
<MudTh>Akcje</MudTh>
|
||||
</HeaderContent>
|
||||
<RowTemplate>
|
||||
<MudTd DataLabel="Nazwa">@context.Name</MudTd>
|
||||
<MudTd DataLabel="Kod">KOD</MudTd>
|
||||
<MudTd DataLabel="EAN"></MudTd>
|
||||
<MudTd DataLabel="Akcje">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Edit"
|
||||
Size="Size.Small"
|
||||
OnClick="() => EditProduct(context.Id)"/>
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Delete"
|
||||
Size="Size.Small"
|
||||
Color="Color.Error"
|
||||
OnClick="() => DeleteProduct(context.Id)"/>
|
||||
</MudTd>
|
||||
</RowTemplate>
|
||||
<NoRecordsContent>
|
||||
<MudText>Brak produktów do wyświetlenia</MudText>
|
||||
</NoRecordsContent>
|
||||
<LoadingContent>
|
||||
Ładowanie...
|
||||
</LoadingContent>
|
||||
</MudTable>
|
||||
|
||||
@if (layers.TotalCount > 0)
|
||||
{
|
||||
<MudGrid Class="mt-4" AlignItems="Center.Center">
|
||||
<MudItem xs="12" sm="6">
|
||||
<MudText Typo="Typo.body2">
|
||||
Wyniki @((layers.Page - 1) * layers.PageSize + 1) - @Math.Min(layers.Page * layers.PageSize, layers.TotalCount)
|
||||
z @layers.TotalCount
|
||||
</MudText>
|
||||
</MudItem>
|
||||
<MudItem xs="12" sm="6" Class="d-flex justify-end">
|
||||
<MudPagination Count="layers.TotalPages"
|
||||
Selected="layers.Page"
|
||||
SelectedChanged="OnPageChanged"
|
||||
ShowFirstButton="true"
|
||||
ShowLastButton="true"/>
|
||||
</MudItem>
|
||||
</MudGrid>
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
using DiunaBI.UI.Shared.Services;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using DiunaBI.Application.DTOModels;
|
||||
using DiunaBI.Application.DTOModels.Common;
|
||||
using MudBlazor;
|
||||
|
||||
namespace DiunaBI.UI.Shared.Components;
|
||||
|
||||
public partial class LayerListComponent : ComponentBase
|
||||
{
|
||||
[Inject] private LayerService LayerService { get; set; } = default!;
|
||||
[Inject] private ISnackbar Snackbar { get; set; } = default!;
|
||||
|
||||
|
||||
private PagedResult<LayerDto> layers = new();
|
||||
private LayerFilterRequest filterRequest = new();
|
||||
private bool isLoading = false;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await LoadProducts();
|
||||
}
|
||||
|
||||
private async Task LoadProducts()
|
||||
{
|
||||
isLoading = true;
|
||||
|
||||
try
|
||||
{
|
||||
layers = await LayerService.GetLayersAsync(filterRequest);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Loading products failed: {ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
isLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SearchProducts()
|
||||
{
|
||||
filterRequest.Page = 1;
|
||||
await LoadProducts();
|
||||
}
|
||||
|
||||
private async Task OnPageChanged(int page)
|
||||
{
|
||||
filterRequest.Page = page;
|
||||
await LoadProducts();
|
||||
}
|
||||
|
||||
private async Task ClearFilters()
|
||||
{
|
||||
filterRequest = new LayerFilterRequest();
|
||||
await LoadProducts();
|
||||
}
|
||||
|
||||
private async Task EditProduct(Guid productId)
|
||||
{
|
||||
// TODO
|
||||
Console.WriteLine($"Edytuj produkt: {productId}");
|
||||
}
|
||||
|
||||
private async Task DeleteProduct(Guid productId)
|
||||
{
|
||||
// TODO
|
||||
Console.WriteLine($"Usuń produkt: {productId}");
|
||||
}
|
||||
}
|
||||
142
src/Backend/DiunaBI.UI.Shared/Components/LoginCard.razor
Normal file
142
src/Backend/DiunaBI.UI.Shared/Components/LoginCard.razor
Normal file
@@ -0,0 +1,142 @@
|
||||
|
||||
@using DiunaBI.UI.Shared.Services
|
||||
@using Microsoft.Extensions.Configuration
|
||||
@inject IJSRuntime JS
|
||||
@inject IConfiguration Configuration
|
||||
@inject AuthService AuthService
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
<MudCard Class="login-card" Elevation="8">
|
||||
<MudCardContent Class="pa-8 d-flex flex-column align-center">
|
||||
<MudText Typo="Typo.h4" Class="mb-4">Witaj w BimAI</MudText>
|
||||
<MudText Typo="Typo.body1" Class="mb-6 text-center">
|
||||
Zaloguj się używając konta Google
|
||||
</MudText>
|
||||
|
||||
<MudButton
|
||||
Variant="Variant.Filled"
|
||||
StartIcon="@Icons.Custom.Brands.Google"
|
||||
Size="Size.Large"
|
||||
OnClick="HandleGoogleSignIn"
|
||||
Disabled="@_isLoading">
|
||||
@if (_isLoading)
|
||||
{
|
||||
<MudProgressCircular Class="mr-3" Size="Size.Small" Indeterminate="true"></MudProgressCircular>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span>Zaloguj z Google</span>
|
||||
}
|
||||
</MudButton>
|
||||
|
||||
@if (!string.IsNullOrEmpty(_errorMessage))
|
||||
{
|
||||
<MudAlert Severity="Severity.Error" Class="mt-4">
|
||||
@_errorMessage
|
||||
</MudAlert>
|
||||
}
|
||||
</MudCardContent>
|
||||
</MudCard>
|
||||
|
||||
@code {
|
||||
private bool _isLoading = false;
|
||||
private string _errorMessage = string.Empty;
|
||||
private static LoginCard? _instance;
|
||||
private bool _isInitialized = false;
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
_instance = this;
|
||||
await InitializeGoogleSignIn();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task InitializeGoogleSignIn()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_isInitialized) return;
|
||||
|
||||
var clientId = Configuration["GoogleAuth:ClientId"];
|
||||
if (string.IsNullOrEmpty(clientId))
|
||||
{
|
||||
throw new Exception("Google ClientId is not configured.");
|
||||
}
|
||||
|
||||
await JS.InvokeVoidAsync("initGoogleSignIn", clientId);
|
||||
_isInitialized = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_errorMessage = "Błąd inicjalizacji Google Sign-In.";
|
||||
Console.Error.WriteLine($"Google Sign-In initialization error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleGoogleSignIn()
|
||||
{
|
||||
try
|
||||
{
|
||||
_isLoading = true;
|
||||
_errorMessage = string.Empty;
|
||||
StateHasChanged();
|
||||
|
||||
await JS.InvokeVoidAsync("requestGoogleSignIn");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_errorMessage = "Błąd podczas logowania. Spróbuj ponownie";
|
||||
_isLoading = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
[JSInvokable]
|
||||
public static async Task OnGoogleSignInSuccess(string accessToken, string fullName, string email, string avatarUrl)
|
||||
{
|
||||
Console.WriteLine($"Google Sign-In Success: {email}");
|
||||
|
||||
if (_instance != null)
|
||||
{
|
||||
var userInfo = new UserInfo
|
||||
{
|
||||
FullName = fullName,
|
||||
Email = email,
|
||||
AvatarUrl = avatarUrl
|
||||
};
|
||||
|
||||
await _instance.AuthService.SetAuthenticationAsync(accessToken, userInfo);
|
||||
|
||||
_instance._isLoading = false;
|
||||
_instance._errorMessage = string.Empty;
|
||||
|
||||
_instance.NavigationManager.NavigateTo("/dashboard", replace: true);
|
||||
|
||||
await _instance.InvokeAsync(() => _instance.StateHasChanged());
|
||||
}
|
||||
}
|
||||
|
||||
[JSInvokable]
|
||||
public static async Task OnGoogleSignInError(string error)
|
||||
{
|
||||
Console.WriteLine($"Google SignIn Error: {error}");
|
||||
|
||||
if (_instance != null)
|
||||
{
|
||||
_instance._isLoading = false;
|
||||
_instance._errorMessage = "Błąd logowania Google. Spróbuj ponownie";
|
||||
await _instance.InvokeAsync(() => _instance.StateHasChanged());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<style>
|
||||
.login-card {
|
||||
max-width: 400px;
|
||||
width: 100%;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
</style>
|
||||
10
src/Backend/DiunaBI.UI.Shared/Components/NavMenu.razor
Normal file
10
src/Backend/DiunaBI.UI.Shared/Components/NavMenu.razor
Normal file
@@ -0,0 +1,10 @@
|
||||
@using Microsoft.AspNetCore.Components.Routing
|
||||
@using MudBlazor
|
||||
<MudNavMenu>
|
||||
<MudNavLink href="dashboard" Icon="@Icons.Material.Filled.Dashboard" Match="NavLinkMatch.All">
|
||||
Dashboard
|
||||
</MudNavLink>
|
||||
<MudNavLink Href="products" Icon="@Icons.Material.Filled.List" Match="NavLinkMatch.All">
|
||||
Produkty
|
||||
</MudNavLink>
|
||||
</MudNavMenu>
|
||||
18
src/Backend/DiunaBI.UI.Shared/Components/Routes.razor
Normal file
18
src/Backend/DiunaBI.UI.Shared/Components/Routes.razor
Normal file
@@ -0,0 +1,18 @@
|
||||
@using Microsoft.AspNetCore.Components.Routing
|
||||
@using MudBlazor
|
||||
|
||||
<Router AppAssembly="@typeof(Routes).Assembly">
|
||||
<Found Context="routeData">
|
||||
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
|
||||
<FocusOnNavigate RouteData="routeData" Selector="h1" />
|
||||
</Found>
|
||||
<NotFound>
|
||||
<LayoutView Layout="@typeof(MainLayout)">
|
||||
<MudCard Elevation="0">
|
||||
<MudText Typo="Typo.h6">
|
||||
Strona nieznaleziona.
|
||||
</MudText>
|
||||
</MudCard>
|
||||
</LayoutView>
|
||||
</NotFound>
|
||||
</Router>
|
||||
30
src/Backend/DiunaBI.UI.Shared/DiunaBI.UI.Shared.csproj
Normal file
30
src/Backend/DiunaBI.UI.Shared/DiunaBI.UI.Shared.csproj
Normal file
@@ -0,0 +1,30 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<SupportedPlatform Include="browser"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="8.0.17"/>
|
||||
<PackageReference Include="MudBlazor" Version="7.0.0"/>
|
||||
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\DiunaBI.Application\DiunaBI.Application.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Interfaces\" />
|
||||
<Folder Include="wwwroot\" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
8
src/Backend/DiunaBI.UI.Shared/EmptyLayout.razor
Normal file
8
src/Backend/DiunaBI.UI.Shared/EmptyLayout.razor
Normal file
@@ -0,0 +1,8 @@
|
||||
@inherits LayoutComponentBase
|
||||
|
||||
<MudThemeProvider/>
|
||||
<MudDialogProvider/>
|
||||
<MudSnackbarProvider/>
|
||||
|
||||
|
||||
@Body
|
||||
@@ -0,0 +1,19 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using DiunaBI.UI.Shared.Services;
|
||||
|
||||
namespace DiunaBI.UI.Shared.Extensions;
|
||||
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
public static IServiceCollection AddSharedServices(this IServiceCollection services, string apiBaseUrl)
|
||||
{
|
||||
// HttpClient for API calls
|
||||
services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(apiBaseUrl) });
|
||||
|
||||
// Services
|
||||
services.AddScoped<AuthService>();
|
||||
services.AddScoped<LayerService>();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
69
src/Backend/DiunaBI.UI.Shared/MainLayout.razor
Normal file
69
src/Backend/DiunaBI.UI.Shared/MainLayout.razor
Normal file
@@ -0,0 +1,69 @@
|
||||
@using MudBlazor
|
||||
@inherits LayoutComponentBase
|
||||
|
||||
<AuthGuard>
|
||||
<MudThemeProvider/>
|
||||
<MudDialogProvider/>
|
||||
<MudSnackbarProvider/>
|
||||
|
||||
<MudLayout>
|
||||
<MudBreakpointProvider OnBreakpointChanged="OnBreakpointChanged"></MudBreakpointProvider>
|
||||
<MudAppBar Elevation="0">
|
||||
<MudIconButton
|
||||
Icon="@Icons.Material.Filled.Menu"
|
||||
Color="Color.Inherit"
|
||||
Edge="Edge.Start"
|
||||
OnClick="ToggleDrawer"
|
||||
Class="mud-hidden-md-up"/>
|
||||
<MudSpacer/>
|
||||
<MudText Typo="Typo.h6">BimAI</MudText>
|
||||
</MudAppBar>
|
||||
|
||||
<MudDrawer @bind-Open="_drawerOpen"
|
||||
Anchor="Anchor.Start"
|
||||
Variant="@_drawerVariant"
|
||||
Elevation="1"
|
||||
ClipMode="DrawerClipMode.Always"
|
||||
Class="mud-width-250">
|
||||
<MudNavMenu>
|
||||
<MudNavLink Href="/dashboard" Icon="@Icons.Material.Filled.Dashboard">Dashboard</MudNavLink>
|
||||
<MudNavLink Href="/products" Icon="@Icons.Material.Filled.Inventory">Products</MudNavLink>
|
||||
</MudNavMenu>
|
||||
</MudDrawer>
|
||||
|
||||
<MudMainContent>
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraExtraLarge" Class="my-4">
|
||||
@Body
|
||||
</MudContainer>
|
||||
</MudMainContent>
|
||||
</MudLayout>
|
||||
</AuthGuard>
|
||||
|
||||
@code {
|
||||
|
||||
private bool _drawerOpen = true;
|
||||
private DrawerVariant _drawerVariant = DrawerVariant.Persistent;
|
||||
|
||||
void ToggleDrawer()
|
||||
{
|
||||
Console.WriteLine($"ToogleDrawer clickkk {DateTime.Now}");
|
||||
_drawerOpen = !_drawerOpen;
|
||||
}
|
||||
|
||||
private void OnBreakpointChanged(Breakpoint breakpoint)
|
||||
{
|
||||
if (breakpoint < Breakpoint.Md)
|
||||
{
|
||||
_drawerVariant = DrawerVariant.Temporary;
|
||||
_drawerOpen = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
_drawerVariant = DrawerVariant.Persistent;
|
||||
_drawerOpen = true;
|
||||
}
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
}
|
||||
8
src/Backend/DiunaBI.UI.Shared/Pages/LayerListPage.razor
Normal file
8
src/Backend/DiunaBI.UI.Shared/Pages/LayerListPage.razor
Normal file
@@ -0,0 +1,8 @@
|
||||
@page "/products"
|
||||
@using DiunaBI.UI.Shared.Components
|
||||
|
||||
<PageTitle>Warstwy</PageTitle>
|
||||
|
||||
<MudContainer MaxWidth="MaxWidth.ExtraExtraLarge">
|
||||
<LayerListComponent />
|
||||
</MudContainer>
|
||||
46
src/Backend/DiunaBI.UI.Shared/Pages/LoginPage.razor
Normal file
46
src/Backend/DiunaBI.UI.Shared/Pages/LoginPage.razor
Normal file
@@ -0,0 +1,46 @@
|
||||
@page "/login"
|
||||
@layout EmptyLayout
|
||||
|
||||
<div class="login-page">
|
||||
<div class="login-container">
|
||||
<LoginCard />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
html, body {
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
height: 100% !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
#app {
|
||||
height: 100% !important;
|
||||
}
|
||||
|
||||
.login-page {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background: linear-gradient(rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0.3)), url('_content/BimAI.UI.Shared/images/login-background.jpg') no-repeat center;
|
||||
background-size: cover;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.login-container {
|
||||
padding: 20px;
|
||||
width: 100%;
|
||||
max-width: 450px;
|
||||
}
|
||||
|
||||
</style>
|
||||
113
src/Backend/DiunaBI.UI.Shared/Services/AuthService.cs
Normal file
113
src/Backend/DiunaBI.UI.Shared/Services/AuthService.cs
Normal file
@@ -0,0 +1,113 @@
|
||||
using System.Text.Json;
|
||||
using Microsoft.JSInterop;
|
||||
|
||||
namespace DiunaBI.UI.Shared.Services;
|
||||
|
||||
public class UserInfo
|
||||
{
|
||||
public string FullName { get; set; } = string.Empty;
|
||||
public string Email { get; set; } = string.Empty;
|
||||
public string AvatarUrl { get; set; } = string.Empty;
|
||||
}
|
||||
public class AuthService
|
||||
{
|
||||
private readonly IJSRuntime _jsRuntime;
|
||||
private bool? _isAuthenticated;
|
||||
private UserInfo? _userInfo = null;
|
||||
|
||||
public event Action<bool>? AuthenticationStateChanged;
|
||||
|
||||
public AuthService(IJSRuntime jsRuntime)
|
||||
{
|
||||
_jsRuntime = jsRuntime;
|
||||
}
|
||||
|
||||
public bool IsAuthenticated => _isAuthenticated ?? false;
|
||||
public UserInfo? CurrentUser => _userInfo;
|
||||
|
||||
public async Task<bool> CheckAuthenticationAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var token = await _jsRuntime.InvokeAsync<string?>("localStorage.getItem", "google_token");
|
||||
var userInfoJson = await _jsRuntime.InvokeAsync<string?>("localStorage.getItem", "user_info");
|
||||
|
||||
_isAuthenticated = !string.IsNullOrEmpty(token);
|
||||
|
||||
if (_isAuthenticated.Value && !string.IsNullOrEmpty(userInfoJson))
|
||||
{
|
||||
_userInfo = JsonSerializer.Deserialize<UserInfo>(userInfoJson);
|
||||
}
|
||||
|
||||
Console.WriteLine($"AuthService.CheckAuthentication: token={(!string.IsNullOrEmpty(token) ? "EXISTS" : "NULL")}, isAuth={_isAuthenticated}");
|
||||
|
||||
return _isAuthenticated.Value;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"AuthService.CheckAuthentication ERROR: {ex.Message}");
|
||||
_isAuthenticated = false;
|
||||
_userInfo = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SetAuthenticationAsync(string token, UserInfo? userInfo = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _jsRuntime.InvokeVoidAsync("localStorage.setItem", "google_token", token);
|
||||
|
||||
if (userInfo != null)
|
||||
{
|
||||
_userInfo = userInfo;
|
||||
var userInfoJson = JsonSerializer.Serialize(userInfo);
|
||||
await _jsRuntime.InvokeVoidAsync("localStorage.setItem", "user_info", userInfoJson);
|
||||
}
|
||||
|
||||
_isAuthenticated = true;
|
||||
Console.WriteLine($"AuthService.SetAuthentication: token saved, user={_userInfo?.Email}");
|
||||
AuthenticationStateChanged?.Invoke(true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"AuthService.SetAuthentication ERROR: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ClearAuthenticationAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
await _jsRuntime.InvokeVoidAsync("localStorage.removeItem", "google_token");
|
||||
await _jsRuntime.InvokeVoidAsync("localStorage.removeItem", "user_info");
|
||||
_isAuthenticated = false;
|
||||
_userInfo = null;
|
||||
Console.WriteLine($"AuthService.ClearAuthentication: token and user ingfo removed");
|
||||
AuthenticationStateChanged?.Invoke(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"AuthService.ClearAuthentication ERROR: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<string?> GetTokenAsync()
|
||||
{
|
||||
if (_isAuthenticated != true)
|
||||
{
|
||||
await CheckAuthenticationAsync();
|
||||
}
|
||||
|
||||
if (_isAuthenticated != true) return null;
|
||||
|
||||
try
|
||||
{
|
||||
return await _jsRuntime.InvokeAsync<string?>("localStorage.getItem", "google_token");
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace DiunaBI.UI.Shared.Services;
|
||||
|
||||
public class GoogleAuthConfig
|
||||
{
|
||||
public string ClientId { get; set; } = string.Empty;
|
||||
}
|
||||
59
src/Backend/DiunaBI.UI.Shared/Services/LayerService.cs
Normal file
59
src/Backend/DiunaBI.UI.Shared/Services/LayerService.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json;
|
||||
using DiunaBI.Application.DTOModels;
|
||||
using DiunaBI.Application.DTOModels.Common;
|
||||
|
||||
namespace DiunaBI.UI.Shared.Services;
|
||||
|
||||
public class LayerService
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
|
||||
public LayerService(HttpClient httpClient)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
}
|
||||
|
||||
private readonly JsonSerializerOptions _jsonOptions = new()
|
||||
{
|
||||
PropertyNameCaseInsensitive = true
|
||||
};
|
||||
|
||||
public async Task<PagedResult<LayerDto>> GetLayersAsync(LayerFilterRequest filterRequest)
|
||||
{
|
||||
var query = $"/api/Layers?start={filterRequest.Page}&limit={filterRequest.Page}";
|
||||
|
||||
if (!string.IsNullOrEmpty(filterRequest.Search))
|
||||
query += $"&name={Uri.EscapeDataString(filterRequest.Search)}";
|
||||
|
||||
/*
|
||||
if (type.HasValue)
|
||||
query += $"&type={type.Value}";
|
||||
*/
|
||||
|
||||
var response = await _httpClient.GetAsync(query);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var json = await response.Content.ReadAsStringAsync();
|
||||
var result = JsonSerializer.Deserialize<PagedResult<LayerDto>>(json, _jsonOptions);
|
||||
|
||||
return result ?? new PagedResult<LayerDto>();
|
||||
}
|
||||
|
||||
public async Task<LayerDto?> GetLayerByIdAsync(Guid id)
|
||||
{
|
||||
var response = await _httpClient.GetAsync($"/api/Layers/{id}");
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
return null;
|
||||
|
||||
return await response.Content.ReadFromJsonAsync<LayerDto>();
|
||||
}
|
||||
|
||||
public async Task<bool> UpdateRecordsAsync(Guid layerId, List<RecordDto> records)
|
||||
{
|
||||
// TODO: Implement if needed - backend doesn't have PUT endpoint yet
|
||||
// For now we don't need it for read-only view
|
||||
return await Task.FromResult(false);
|
||||
}
|
||||
}
|
||||
12
src/Backend/DiunaBI.UI.Shared/_Imports.razor
Normal file
12
src/Backend/DiunaBI.UI.Shared/_Imports.razor
Normal file
@@ -0,0 +1,12 @@
|
||||
@using System.Net.Http
|
||||
@using System.Net.Http.Json
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
@using Microsoft.AspNetCore.Components.Routing
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
@using static Microsoft.AspNetCore.Components.Web.RenderMode
|
||||
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
||||
@using Microsoft.JSInterop
|
||||
@using DiunaBI.UI.Shared
|
||||
@using DiunaBI.UI.Shared.Components
|
||||
@using DiunaBI.Application.DTOModels
|
||||
@using MudBlazor
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 965 KiB |
119
src/Backend/DiunaBI.UI.Shared/wwwroot/js/auth.js
Normal file
119
src/Backend/DiunaBI.UI.Shared/wwwroot/js/auth.js
Normal file
@@ -0,0 +1,119 @@
|
||||
let googleClient = null;
|
||||
let isSigningIn = false;
|
||||
|
||||
function waitForGoogleApi() {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (window.google?.accounts?.oauth2) {
|
||||
resolve(window.google);
|
||||
return;
|
||||
}
|
||||
|
||||
const maxAttempts = 20;
|
||||
let attempts = 0;
|
||||
|
||||
const checkGoogle = setInterval(() => {
|
||||
attempts++;
|
||||
if (window.google?.accounts?.oauth2) {
|
||||
clearInterval(checkGoogle);
|
||||
resolve(window.google);
|
||||
} else if (attempts >= maxAttempts) {
|
||||
clearInterval(checkGoogle);
|
||||
reject(new Error('Google OAuth2 API failed to load within the timeout period'));
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
|
||||
async function handleAuthError(error, context = '') {
|
||||
const errorMessage = error?.message || error?.type || error?.toString() || 'Unknown error';
|
||||
const fullError = `${context}: ${errorMessage}`;
|
||||
console.error('Google Auth Error:', { context, error, fullError });
|
||||
await DotNet.invokeMethodAsync('BimAI.UI.Shared', 'OnGoogleSignInError', fullError);
|
||||
}
|
||||
|
||||
async function fetchUserInfo(accessToken) {
|
||||
const response = await fetch('https://www.googleapis.com/oauth2/v3/userinfo', {
|
||||
headers: { 'Authorization': `Bearer ${accessToken}` }
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text();
|
||||
console.error('Failed to fetch user info:', errorText);
|
||||
await DotNet.invokeMethodAsync('BimAI.UI.Shared', 'OnGoogleSignInError',
|
||||
`Failed to fetch user info: HTTP ${response.status}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
window.initGoogleSignIn = async function(clientId) {
|
||||
if (googleClient) {
|
||||
return googleClient;
|
||||
}
|
||||
|
||||
try {
|
||||
const google = await waitForGoogleApi();
|
||||
|
||||
googleClient = google.accounts.oauth2.initTokenClient({
|
||||
client_id: clientId,
|
||||
scope: 'email profile',
|
||||
callback: async (tokenResponse) => {
|
||||
try {
|
||||
if (tokenResponse.error) {
|
||||
console.error('Token response error:', tokenResponse.error);
|
||||
await DotNet.invokeMethodAsync('BimAI.UI.Shared', 'OnGoogleSignInError',
|
||||
tokenResponse.error);
|
||||
return;
|
||||
}
|
||||
|
||||
const userInfo = await fetchUserInfo(tokenResponse.access_token);
|
||||
if (!userInfo) return;
|
||||
|
||||
await DotNet.invokeMethodAsync('BimAI.UI.Shared', 'OnGoogleSignInSuccess',
|
||||
tokenResponse.access_token,
|
||||
userInfo.name || '',
|
||||
userInfo.email || '',
|
||||
userInfo.picture || ''
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Callback error:', error);
|
||||
await DotNet.invokeMethodAsync('BimAI.UI.Shared', 'OnGoogleSignInError',
|
||||
error.message || 'Unknown callback error');
|
||||
} finally {
|
||||
isSigningIn = false;
|
||||
}
|
||||
},
|
||||
error_callback: async (error) => {
|
||||
console.error('OAuth flow error:', error);
|
||||
await DotNet.invokeMethodAsync('BimAI.UI.Shared', 'OnGoogleSignInError',
|
||||
error.type || 'OAuth flow error');
|
||||
isSigningIn = false;
|
||||
}
|
||||
});
|
||||
|
||||
return googleClient;
|
||||
} catch (error) {
|
||||
console.error('Initiaxcrun xctrace list deviceslization error:', error);
|
||||
await DotNet.invokeMethodAsync('BimAI.UI.Shared', 'OnGoogleSignInError',
|
||||
error.message || 'Failed to initialize Google Sign-In');
|
||||
isSigningIn = false;
|
||||
}
|
||||
};
|
||||
|
||||
window.requestGoogleSignIn = async function() {
|
||||
if (isSigningIn) {
|
||||
console.log('Sign-in already in progress');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!googleClient) {
|
||||
console.error('Google Sign-In not initialized');
|
||||
await DotNet.invokeMethodAsync('BimAI.UI.Shared', 'OnGoogleSignInError',
|
||||
'Google Sign-In not initialized. Call initGoogleSignIn first.');
|
||||
return;
|
||||
}
|
||||
|
||||
isSigningIn = true;
|
||||
googleClient.requestAccessToken();
|
||||
};
|
||||
36
src/Backend/DiunaBI.UI.Web/Components/App.razor
Normal file
36
src/Backend/DiunaBI.UI.Web/Components/App.razor
Normal file
@@ -0,0 +1,36 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<base href="/" />
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" rel="stylesheet" />
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
|
||||
<link href="_content/MudBlazor/MudBlazor.min.css" rel="stylesheet" />
|
||||
<link href="BimAI.UI.Web.styles.css" rel="stylesheet" />
|
||||
<script src="https://accounts.google.com/gsi/client" async defer></script>
|
||||
<HeadOutlet />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<DiunaBI.UI.Shared.Components.Routes @rendermode="InteractiveServer" />
|
||||
|
||||
|
||||
<div id="blazor-error-ui">
|
||||
<environment include="Staging,Production">
|
||||
An error has occurred. This application may no longer respond until reloaded.
|
||||
</environment>
|
||||
<environment include="Development">
|
||||
An unhandled exception has occurred. See browser dev tools for details.
|
||||
</environment>
|
||||
<a href="" class="reload">Reload</a>
|
||||
<a class="dismiss">🗙</a>
|
||||
</div>
|
||||
|
||||
<script src="_framework/blazor.web.js"></script>
|
||||
<script src="_content/MudBlazor/MudBlazor.min.js"></script>
|
||||
<script src="_content/DiunaBI.UI.Shared/js/auth.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,9 @@
|
||||
@inherits LayoutComponentBase
|
||||
|
||||
@Body
|
||||
|
||||
<div id="blazor-error-ui">
|
||||
An unhandled error has occurred.
|
||||
<a href="" class="reload">Reload</a>
|
||||
<a class="dismiss">🗙</a>
|
||||
</div>
|
||||
@@ -0,0 +1,18 @@
|
||||
#blazor-error-ui {
|
||||
background: lightyellow;
|
||||
bottom: 0;
|
||||
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
|
||||
display: none;
|
||||
left: 0;
|
||||
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
#blazor-error-ui .dismiss {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
right: 0.75rem;
|
||||
top: 0.5rem;
|
||||
}
|
||||
36
src/Backend/DiunaBI.UI.Web/Components/Pages/Error.razor
Normal file
36
src/Backend/DiunaBI.UI.Web/Components/Pages/Error.razor
Normal file
@@ -0,0 +1,36 @@
|
||||
@page "/Error"
|
||||
@using System.Diagnostics
|
||||
|
||||
<PageTitle>Error</PageTitle>
|
||||
|
||||
<h1 class="text-danger">Error.</h1>
|
||||
<h2 class="text-danger">An error occurred while processing your request.</h2>
|
||||
|
||||
@if (ShowRequestId)
|
||||
{
|
||||
<p>
|
||||
<strong>Request ID:</strong> <code>@RequestId</code>
|
||||
</p>
|
||||
}
|
||||
|
||||
<h3>Development Mode</h3>
|
||||
<p>
|
||||
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
|
||||
</p>
|
||||
<p>
|
||||
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
|
||||
It can result in displaying sensitive information from exceptions to end users.
|
||||
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
|
||||
and restarting the app.
|
||||
</p>
|
||||
|
||||
@code{
|
||||
[CascadingParameter] private HttpContext? HttpContext { get; set; }
|
||||
|
||||
private string? RequestId { get; set; }
|
||||
private bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
|
||||
|
||||
protected override void OnInitialized() =>
|
||||
RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier;
|
||||
|
||||
}
|
||||
13
src/Backend/DiunaBI.UI.Web/Components/_Imports.razor
Normal file
13
src/Backend/DiunaBI.UI.Web/Components/_Imports.razor
Normal file
@@ -0,0 +1,13 @@
|
||||
@using System.Net.Http
|
||||
@using System.Net.Http.Json
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
@using Microsoft.AspNetCore.Components.Routing
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
@using static Microsoft.AspNetCore.Components.Web.RenderMode
|
||||
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
||||
@using Microsoft.JSInterop
|
||||
@using DiunaBI.UI.Web
|
||||
@using DiunaBI.UI.Web.Components
|
||||
@using DiunaBI.UI.Shared
|
||||
@using DiunaBI.UI.Shared.Components
|
||||
@using MudBlazor
|
||||
17
src/Backend/DiunaBI.UI.Web/DiunaBI.UI.Web.csproj
Normal file
17
src/Backend/DiunaBI.UI.Web/DiunaBI.UI.Web.csproj
Normal file
@@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MudBlazor" Version="7.0.0"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\DiunaBI.UI.Shared\DiunaBI.UI.Shared.csproj"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
37
src/Backend/DiunaBI.UI.Web/Program.cs
Normal file
37
src/Backend/DiunaBI.UI.Web/Program.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using DiunaBI.UI.Shared;
|
||||
using DiunaBI.UI.Shared.Extensions;
|
||||
using DiunaBI.UI.Shared.Services;
|
||||
using DiunaBI.UI.Web.Components;
|
||||
using MudBlazor.Services;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
builder.Services.AddRazorComponents()
|
||||
.AddInteractiveServerComponents();
|
||||
builder.Services.AddMudServices();
|
||||
|
||||
var apiBaseUrl = builder.Configuration["ApiSettings:BaseUrl"]
|
||||
?? throw new InvalidOperationException("ApiSettings:BaseUrl is not configured");
|
||||
builder.Services.AddSharedServices(apiBaseUrl);
|
||||
|
||||
builder.Services.AddScoped<AuthService>();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
if (!app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseExceptionHandler("/Error", createScopeForErrors: true);
|
||||
app.UseHsts();
|
||||
}
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
app.UseStaticFiles();
|
||||
app.UseAntiforgery();
|
||||
|
||||
app.MapGet("/health", () => Results.Ok(new { status = "OK", timestamp = DateTime.UtcNow }));
|
||||
|
||||
app.MapRazorComponents<App>()
|
||||
.AddInteractiveServerRenderMode()
|
||||
.AddAdditionalAssemblies(typeof(MainLayout).Assembly);
|
||||
|
||||
app.Run();
|
||||
13
src/Backend/DiunaBI.UI.Web/Properties/launchSettings.json
Normal file
13
src/Backend/DiunaBI.UI.Web/Properties/launchSettings.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"profiles": {
|
||||
"dev": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "https://localhost:7246;http://localhost:5246",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
22
src/Backend/DiunaBI.UI.Web/appsettings.Production.json
Normal file
22
src/Backend/DiunaBI.UI.Web/appsettings.Production.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"ApiSettings": {
|
||||
"BaseUrl": "#{api-base-url}#"
|
||||
},
|
||||
"GoogleAuth": {
|
||||
"ClientId": "#{google-auth-client-id}#"
|
||||
},
|
||||
"Kestrel": {
|
||||
"Endpoints": {
|
||||
"Http": {
|
||||
"Url": "http://0.0.0.0:7143"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
51
src/Backend/DiunaBI.UI.Web/wwwroot/app.css
Normal file
51
src/Backend/DiunaBI.UI.Web/wwwroot/app.css
Normal file
@@ -0,0 +1,51 @@
|
||||
html, body {
|
||||
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
a, .btn-link {
|
||||
color: #006bb7;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
color: #fff;
|
||||
background-color: #1b6ec2;
|
||||
border-color: #1861ac;
|
||||
}
|
||||
|
||||
.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
|
||||
box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding-top: 1.1rem;
|
||||
}
|
||||
|
||||
h1:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.valid.modified:not([type=checkbox]) {
|
||||
outline: 1px solid #26b050;
|
||||
}
|
||||
|
||||
.invalid {
|
||||
outline: 1px solid #e50000;
|
||||
}
|
||||
|
||||
.validation-message {
|
||||
color: #e50000;
|
||||
}
|
||||
|
||||
.blazor-error-boundary {
|
||||
background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
|
||||
padding: 1rem 1rem 1rem 3.7rem;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.blazor-error-boundary::after {
|
||||
content: "An error has occurred."
|
||||
}
|
||||
|
||||
.darker-border-checkbox.form-check-input {
|
||||
border-color: #929292;
|
||||
}
|
||||
7
src/Backend/DiunaBI.UI.Web/wwwroot/bootstrap/bootstrap.min.css
vendored
Normal file
7
src/Backend/DiunaBI.UI.Web/wwwroot/bootstrap/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
BIN
src/Backend/DiunaBI.UI.Web/wwwroot/favicon.png
Normal file
BIN
src/Backend/DiunaBI.UI.Web/wwwroot/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
@@ -15,6 +15,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiunaBI.Application", "Diun
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiunaBI.Infrastructure", "DiunaBI.Infrastructure\DiunaBI.Infrastructure.csproj", "{0B2E03F3-A1F7-4C7F-BCA7-386979C93346}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiunaBI.UI.Shared", "DiunaBI.UI.Shared\DiunaBI.UI.Shared.csproj", "{1F9340A0-8D4F-46C2-80C6-1687778A6D20}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DiunaBI.UI.Web", "DiunaBI.UI.Web\DiunaBI.UI.Web.csproj", "{28F6702F-400A-4378-828B-02E111EE7EFE}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -52,5 +56,13 @@ Global
|
||||
{0B2E03F3-A1F7-4C7F-BCA7-386979C93346}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{0B2E03F3-A1F7-4C7F-BCA7-386979C93346}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{0B2E03F3-A1F7-4C7F-BCA7-386979C93346}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{1F9340A0-8D4F-46C2-80C6-1687778A6D20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{1F9340A0-8D4F-46C2-80C6-1687778A6D20}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{1F9340A0-8D4F-46C2-80C6-1687778A6D20}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{1F9340A0-8D4F-46C2-80C6-1687778A6D20}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{28F6702F-400A-4378-828B-02E111EE7EFE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{28F6702F-400A-4378-828B-02E111EE7EFE}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{28F6702F-400A-4378-828B-02E111EE7EFE}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{28F6702F-400A-4378-828B-02E111EE7EFE}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
||||
Reference in New Issue
Block a user