diff --git a/src/dotnet/QuantEngine.Web/Infrastructure/CustomAuthenticationStateProvider.cs b/src/dotnet/QuantEngine.Web/Client/Infrastructure/CustomAuthenticationStateProvider.cs similarity index 73% rename from src/dotnet/QuantEngine.Web/Infrastructure/CustomAuthenticationStateProvider.cs rename to src/dotnet/QuantEngine.Web/Client/Infrastructure/CustomAuthenticationStateProvider.cs index 6637d86..5e71f64 100644 --- a/src/dotnet/QuantEngine.Web/Infrastructure/CustomAuthenticationStateProvider.cs +++ b/src/dotnet/QuantEngine.Web/Client/Infrastructure/CustomAuthenticationStateProvider.cs @@ -1,16 +1,16 @@ using System.Security.Claims; 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 { - private readonly ProtectedLocalStorage _localStorage; + private readonly LocalStorageService _localStorage; private readonly ClaimsPrincipal _anonymous = new ClaimsPrincipal(new ClaimsIdentity()); private const string StorageKey = "quant_admin_session"; - public CustomAuthenticationStateProvider(ProtectedLocalStorage localStorage) + public CustomAuthenticationStateProvider(LocalStorageService localStorage) { _localStorage = localStorage; } @@ -19,11 +19,9 @@ namespace QuantEngine.Web.Infrastructure { try { - // ProtectedLocalStorage call will throw an exception during pre-rendering - var result = await _localStorage.GetAsync(StorageKey); - if (result.Success && !string.IsNullOrEmpty(result.Value)) + var username = await _localStorage.GetAsync(StorageKey); + if (!string.IsNullOrEmpty(username)) { - var username = result.Value; var identity = new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, username), @@ -36,7 +34,7 @@ namespace QuantEngine.Web.Infrastructure } catch { - // Return anonymous state during pre-rendering or if storage reading fails + // Return anonymous if localStorage isn't ready } return new AuthenticationState(_anonymous); diff --git a/src/dotnet/QuantEngine.Web/Components/Layout/MainLayout.razor b/src/dotnet/QuantEngine.Web/Client/Layout/MainLayout.razor similarity index 79% rename from src/dotnet/QuantEngine.Web/Components/Layout/MainLayout.razor rename to src/dotnet/QuantEngine.Web/Client/Layout/MainLayout.razor index 00b3480..2f03cbe 100644 --- a/src/dotnet/QuantEngine.Web/Components/Layout/MainLayout.razor +++ b/src/dotnet/QuantEngine.Web/Client/Layout/MainLayout.razor @@ -1,11 +1,10 @@ @inherits LayoutComponentBase -@inject Microsoft.AspNetCore.Hosting.IWebHostEnvironment WebHostEnvironment +@inject HttpClient Http @inject AuthenticationStateProvider AuthStateProvider @inject NavigationManager NavigationManager -@using System.IO -@using System.Text.Json +@using System.Net.Http.Json @using Microsoft.FluentUI.AspNetCore.Components -@using QuantEngine.Web.Infrastructure +@using QuantEngine.Web.Client.Infrastructure @@ -77,25 +76,15 @@ private string appVersion = "Local Debug"; private string buildTime = "N/A"; - protected override void OnInitialized() + protected override async Task OnInitializedAsync() { try { - var versionFilePath = Path.Combine(WebHostEnvironment.WebRootPath, "version.json"); - if (File.Exists(versionFilePath)) + var versionInfo = await Http.GetFromJsonAsync("version.json"); + if (versionInfo != null) { - var jsonContent = File.ReadAllText(versionFilePath); - using var doc = System.Text.Json.JsonDocument.Parse(jsonContent); - 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"; - } + appVersion = versionInfo.Version ?? "Local Debug"; + buildTime = versionInfo.Built ?? "N/A"; } } catch @@ -110,5 +99,11 @@ await customProvider.MarkUserAsLoggedOutAsync(); NavigationManager.NavigateTo("login"); } + + private class VersionInfo + { + public string? Version { get; set; } + public string? Built { get; set; } + } } diff --git a/src/dotnet/QuantEngine.Web/Components/Layout/MainLayout.razor.css b/src/dotnet/QuantEngine.Web/Client/Layout/MainLayout.razor.css similarity index 100% rename from src/dotnet/QuantEngine.Web/Components/Layout/MainLayout.razor.css rename to src/dotnet/QuantEngine.Web/Client/Layout/MainLayout.razor.css diff --git a/src/dotnet/QuantEngine.Web/Components/Layout/NavMenu.razor b/src/dotnet/QuantEngine.Web/Client/Layout/NavMenu.razor similarity index 100% rename from src/dotnet/QuantEngine.Web/Components/Layout/NavMenu.razor rename to src/dotnet/QuantEngine.Web/Client/Layout/NavMenu.razor diff --git a/src/dotnet/QuantEngine.Web/Components/Layout/NavMenu.razor.css b/src/dotnet/QuantEngine.Web/Client/Layout/NavMenu.razor.css similarity index 100% rename from src/dotnet/QuantEngine.Web/Components/Layout/NavMenu.razor.css rename to src/dotnet/QuantEngine.Web/Client/Layout/NavMenu.razor.css diff --git a/src/dotnet/QuantEngine.Web/Client/Pages/Collection.razor b/src/dotnet/QuantEngine.Web/Client/Pages/Collection.razor index 06ab307..0914e8a 100644 --- a/src/dotnet/QuantEngine.Web/Client/Pages/Collection.razor +++ b/src/dotnet/QuantEngine.Web/Client/Pages/Collection.razor @@ -1,5 +1,5 @@ @page "/collection" -@using QuantEngine.Web.Services +@using QuantEngine.Web.Client.Services @inject ApiClient ApiClient @inject ILogger Logger diff --git a/src/dotnet/QuantEngine.Web/Components/Pages/Dashboard.razor b/src/dotnet/QuantEngine.Web/Client/Pages/Dashboard.razor similarity index 86% rename from src/dotnet/QuantEngine.Web/Components/Pages/Dashboard.razor rename to src/dotnet/QuantEngine.Web/Client/Pages/Dashboard.razor index d924141..532b478 100644 --- a/src/dotnet/QuantEngine.Web/Components/Pages/Dashboard.razor +++ b/src/dotnet/QuantEngine.Web/Client/Pages/Dashboard.razor @@ -1,6 +1,6 @@ @page "/" @using QuantEngine.Core.Infrastructure -@inject IWebHostEnvironment Environment +@inject HttpClient Http Quant Engine - Dashboard @@ -96,15 +96,26 @@ private string RawFeedLabel = "DISCONNECTED"; 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")); - var report = OperationalReportLoader.Load(ReportPath); - Sections.AddRange(report.Sections); - SectionCountLabel = report.SectionCount.ToString(); - GeneratedAtLabel = report.GeneratedAt; - SourceLabel = report.SourceJson; - ReportStateLabel = Sections.Count > 0 ? "READY" : "DATA_MISSING"; - ReportChipLabel = Sections.Count > 0 ? "READY" : "DATA_MISSING"; + try + { + var report = await Http.GetFromJsonAsync("api/operational-report"); + if (report != null) + { + Sections.Clear(); + Sections.AddRange(report.Sections); + 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"; + } } } diff --git a/src/dotnet/QuantEngine.Web/Components/Pages/Login.razor b/src/dotnet/QuantEngine.Web/Client/Pages/Login.razor similarity index 96% rename from src/dotnet/QuantEngine.Web/Components/Pages/Login.razor rename to src/dotnet/QuantEngine.Web/Client/Pages/Login.razor index 74988dd..0779a4b 100644 --- a/src/dotnet/QuantEngine.Web/Components/Pages/Login.razor +++ b/src/dotnet/QuantEngine.Web/Client/Pages/Login.razor @@ -2,7 +2,7 @@ @attribute [AllowAnonymous] @inject AuthenticationStateProvider AuthStateProvider @inject NavigationManager NavigationManager -@inject IConfiguration Configuration +@inject HttpClient Http 로그인 - QuantEngine @@ -281,11 +281,9 @@ try { - // Verify against configurations in appsettings.json - var expectedUser = Configuration["AdminSettings:Username"] ?? "admin"; - var expectedPass = Configuration["AdminSettings:Password"] ?? "quant123!"; + var response = await Http.PostAsJsonAsync("api/auth/login", new { Username, Password }); - if (Username == expectedUser && Password == expectedPass) + if (response.IsSuccessStatusCode) { var customProvider = (CustomAuthenticationStateProvider)AuthStateProvider; await customProvider.MarkUserAsAuthenticatedAsync(Username); diff --git a/src/dotnet/QuantEngine.Web/Components/Pages/NotFound.razor b/src/dotnet/QuantEngine.Web/Client/Pages/NotFound.razor similarity index 100% rename from src/dotnet/QuantEngine.Web/Components/Pages/NotFound.razor rename to src/dotnet/QuantEngine.Web/Client/Pages/NotFound.razor diff --git a/src/dotnet/QuantEngine.Web/Components/Pages/Operations.razor b/src/dotnet/QuantEngine.Web/Client/Pages/Operations.razor similarity index 84% rename from src/dotnet/QuantEngine.Web/Components/Pages/Operations.razor rename to src/dotnet/QuantEngine.Web/Client/Pages/Operations.razor index fa21792..a93a201 100644 --- a/src/dotnet/QuantEngine.Web/Components/Pages/Operations.razor +++ b/src/dotnet/QuantEngine.Web/Client/Pages/Operations.razor @@ -1,6 +1,6 @@ @page "/operations" @using QuantEngine.Core.Infrastructure -@inject IWebHostEnvironment Environment +@inject HttpClient Http Quant Engine - Operations @@ -97,19 +97,29 @@ protected override async Task OnInitializedAsync() { - ReportPath = Path.GetFullPath(Path.Combine(Environment.ContentRootPath, "..", "..", "..", "Temp", "operational_report.json")); + try + { + var report = await Http.GetFromJsonAsync("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); - SchemaVersion = report.SchemaVersion; - SourceJson = report.SourceJson; - GeneratedAt = report.GeneratedAt; - Sections.AddRange(report.Sections); + HighlightSections.Clear(); + HighlightSections.AddRange(Sections.Take(4)); - HighlightSections.Clear(); - HighlightSections.AddRange(Sections.Take(4)); - - SectionCountLabel = report.SectionCount.ToString(); - 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"; + } } } diff --git a/src/dotnet/QuantEngine.Web/Client/Program.cs b/src/dotnet/QuantEngine.Web/Client/Program.cs new file mode 100644 index 0000000..89b6b09 --- /dev/null +++ b/src/dotnet/QuantEngine.Web/Client/Program.cs @@ -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(); + +// Authentication setup in WebAssembly client +builder.Services.AddAuthorizationCore(); +builder.Services.AddCascadingAuthenticationState(); +builder.Services.AddScoped(); + +// HttpClient register (API-First standard) +builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); + +await builder.Build().RunAsync(); diff --git a/src/dotnet/QuantEngine.Web/Client/QuantEngine.Web.Client.csproj b/src/dotnet/QuantEngine.Web/Client/QuantEngine.Web.Client.csproj new file mode 100644 index 0000000..ad69745 --- /dev/null +++ b/src/dotnet/QuantEngine.Web/Client/QuantEngine.Web.Client.csproj @@ -0,0 +1,23 @@ + + + + net10.0 + enable + enable + true + Default + + + + + + + + + + + + + + + diff --git a/src/dotnet/QuantEngine.Web/Components/RedirectToLogin.razor b/src/dotnet/QuantEngine.Web/Client/RedirectToLogin.razor similarity index 100% rename from src/dotnet/QuantEngine.Web/Components/RedirectToLogin.razor rename to src/dotnet/QuantEngine.Web/Client/RedirectToLogin.razor diff --git a/src/dotnet/QuantEngine.Web/Services/ApiClient.cs b/src/dotnet/QuantEngine.Web/Client/Services/ApiClient.cs similarity index 91% rename from src/dotnet/QuantEngine.Web/Services/ApiClient.cs rename to src/dotnet/QuantEngine.Web/Client/Services/ApiClient.cs index b036a6b..cc03d04 100644 --- a/src/dotnet/QuantEngine.Web/Services/ApiClient.cs +++ b/src/dotnet/QuantEngine.Web/Client/Services/ApiClient.cs @@ -2,19 +2,16 @@ using System.Net.Http.Json; using System.Text.Json.Serialization; using QuantEngine.Core.Interfaces; -namespace QuantEngine.Web.Services; +namespace QuantEngine.Web.Client.Services; public class ApiClient { private readonly HttpClient _http; private readonly ILogger _logger; - private string BaseUrl { get; set; } - public ApiClient(HttpClient http, ILogger logger) { _http = http; _logger = logger; - BaseUrl = "http://localhost:5001"; // Default for Blazor Server } // Collection API Methods @@ -23,7 +20,7 @@ public class ApiClient { try { - return await _http.GetFromJsonAsync($"{BaseUrl}/api/collection/state"); + return await _http.GetFromJsonAsync("api/collection/state"); } catch (Exception ex) { @@ -36,7 +33,7 @@ public class ApiClient { try { - return await _http.GetFromJsonAsync($"{BaseUrl}/api/collection/runs?limit={limit}"); + return await _http.GetFromJsonAsync($"api/collection/runs?limit={limit}"); } catch (Exception ex) { @@ -49,7 +46,7 @@ public class ApiClient { try { - return await _http.GetFromJsonAsync($"{BaseUrl}/api/collection/runs/{runId}/snapshots"); + return await _http.GetFromJsonAsync($"api/collection/runs/{runId}/snapshots"); } catch (Exception ex) { @@ -62,7 +59,7 @@ public class ApiClient { try { - return await _http.GetFromJsonAsync($"{BaseUrl}/api/collection/runs/{runId}/errors?limit={limit}"); + return await _http.GetFromJsonAsync($"api/collection/runs/{runId}/errors?limit={limit}"); } catch (Exception ex) { @@ -75,7 +72,7 @@ public class ApiClient { try { - var response = await _http.PostAsJsonAsync($"{BaseUrl}/api/collection/run", new { }); + var response = await _http.PostAsJsonAsync("api/collection/run", new { }); if (response.IsSuccessStatusCode) { return await response.Content.ReadFromJsonAsync(); diff --git a/src/dotnet/QuantEngine.Web/Client/Services/LocalStorageService.cs b/src/dotnet/QuantEngine.Web/Client/Services/LocalStorageService.cs new file mode 100644 index 0000000..bd175f4 --- /dev/null +++ b/src/dotnet/QuantEngine.Web/Client/Services/LocalStorageService.cs @@ -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(string key, T value) + { + var json = JsonSerializer.Serialize(value); + await _js.InvokeVoidAsync("localStorage.setItem", key, json); + } + + public async Task GetAsync(string key) + { + try + { + var json = await _js.InvokeAsync("localStorage.getItem", key); + if (string.IsNullOrEmpty(json)) + { + return default; + } + return JsonSerializer.Deserialize(json); + } + catch + { + return default; + } + } + + public async Task DeleteAsync(string key) + { + await _js.InvokeVoidAsync("localStorage.removeItem", key); + } + } +} diff --git a/src/dotnet/QuantEngine.Web/Client/_Imports.razor b/src/dotnet/QuantEngine.Web/Client/_Imports.razor index c5994ea..b98bd91 100644 --- a/src/dotnet/QuantEngine.Web/Client/_Imports.razor +++ b/src/dotnet/QuantEngine.Web/Client/_Imports.razor @@ -8,7 +8,10 @@ @using Microsoft.JSInterop @using Microsoft.FluentUI.AspNetCore.Components @using Microsoft.FluentUI.AspNetCore.Components.Icons -@using QuantEngine.Web -@using QuantEngine.Web.Components -@using QuantEngine.Web.Components.Layout -@using QuantEngine.Web.Services +@using QuantEngine.Web.Client +@using QuantEngine.Web.Client.Pages +@using QuantEngine.Web.Client.Layout +@using QuantEngine.Web.Client.Infrastructure +@using QuantEngine.Web.Client.Services +@using Microsoft.AspNetCore.Components.Authorization +@using Microsoft.AspNetCore.Authorization diff --git a/src/dotnet/QuantEngine.Web/Components/App.razor b/src/dotnet/QuantEngine.Web/Components/App.razor index d3f356f..cf436ec 100644 --- a/src/dotnet/QuantEngine.Web/Components/App.razor +++ b/src/dotnet/QuantEngine.Web/Components/App.razor @@ -13,12 +13,12 @@ - + - + diff --git a/src/dotnet/QuantEngine.Web/Components/Routes.razor b/src/dotnet/QuantEngine.Web/Components/Routes.razor index 61d858b..8fe12d7 100644 --- a/src/dotnet/QuantEngine.Web/Components/Routes.razor +++ b/src/dotnet/QuantEngine.Web/Components/Routes.razor @@ -1,7 +1,11 @@ +@using QuantEngine.Web.Client +@using QuantEngine.Web.Client.Pages +@using QuantEngine.Web.Client.Layout + - + - + diff --git a/src/dotnet/QuantEngine.Web/Program.cs b/src/dotnet/QuantEngine.Web/Program.cs index 8e299e1..54f8294 100644 --- a/src/dotnet/QuantEngine.Web/Program.cs +++ b/src/dotnet/QuantEngine.Web/Program.cs @@ -1,7 +1,7 @@ using QuantEngine.Web.Components; -using QuantEngine.Web.Services; using QuantEngine.Infrastructure.Data; using Microsoft.AspNetCore.Components.Authorization; +using QuantEngine.Web.Infrastructure; using QuantEngine.Infrastructure.Repositories; using QuantEngine.Infrastructure.Services; using QuantEngine.Core.Interfaces; @@ -10,7 +10,8 @@ using System.Text.Json; using static QuantEngine.Application.Services.DataCollectionService; using Microsoft.FluentUI.AspNetCore.Components; using Serilog; -using QuantEngine.Web.Infrastructure; +using QuantEngine.Web.Client.Infrastructure; +using QuantEngine.Web.Client.Services; using QuantEngine.Web.Endpoints; // Serilog Configuration with Telegram Sink @@ -25,10 +26,11 @@ builder.Host.UseSerilog(); // Add services to the container. builder.Services.AddRazorComponents() - .AddInteractiveServerComponents(); + .AddInteractiveWebAssemblyComponents(); -// Authentication and Custom State Provider +// Authentication and Custom State Provider (Shared client components) builder.Services.AddCascadingAuthenticationState(); +builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddAuthorizationCore(); @@ -100,6 +102,32 @@ app.MapStaticAssets(); // Collection API Endpoints (must be before MapRazorComponents) 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) => { var rows = await reader.ReadAsync(domain, limit ?? 500); @@ -144,7 +172,14 @@ app.MapPost("/api/history/{domain}", async (string domain, JsonElement payload, }); app.MapRazorComponents() - .AddInteractiveServerRenderMode(); + .AddInteractiveWebAssemblyRenderMode() + .AddAdditionalAssemblies(typeof(QuantEngine.Web.Client._Imports).Assembly); app.Run(); +public class LoginRequest +{ + public string Username { get; set; } = ""; + public string Password { get; set; } = ""; +} + diff --git a/src/dotnet/QuantEngine.Web/QuantEngine.Web.csproj b/src/dotnet/QuantEngine.Web/QuantEngine.Web.csproj index 6cd066a..38d8a2b 100644 --- a/src/dotnet/QuantEngine.Web/QuantEngine.Web.csproj +++ b/src/dotnet/QuantEngine.Web/QuantEngine.Web.csproj @@ -4,12 +4,22 @@ + + + + + + + + + + diff --git a/src/dotnet/QuantEngine.sln b/src/dotnet/QuantEngine.sln index fe56a5d..470e55d 100644 --- a/src/dotnet/QuantEngine.sln +++ b/src/dotnet/QuantEngine.sln @@ -1,4 +1,4 @@ - + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.0.31903.59 @@ -15,6 +15,9 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuantEngine.Core.Tests", "Q EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuantEngine.Tools", "QuantEngine.Tools\QuantEngine.Tools.csproj", "{E2B1A0A1-7B13-4A4D-9B31-9C7F4F27A6A1}" EndProject + +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QuantEngine.Web.Client", "QuantEngine.Web\Client\QuantEngine.Web.Client.csproj", "{C5F2F3BD-1258-40FC-803A-EE7EEC928107}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution 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|x86.ActiveCfg = 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 GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + EndGlobal