wip - adding demo front-end to play with access service
This commit is contained in:
		
							parent
							
								
									c04a603d56
								
							
						
					
					
						commit
						44a3628489
					
				|  | @ -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="Blazor.Bootstrap" Version="3.3.1" /> | ||||
|   </ItemGroup> | ||||
| 
 | ||||
|   <ItemGroup> | ||||
|     <ProjectReference Include="..\AccessQueueService\AccessQueueService.csproj" /> | ||||
|   </ItemGroup> | ||||
| 
 | ||||
| </Project> | ||||
|  | @ -0,0 +1,23 @@ | |||
| <!DOCTYPE html> | ||||
| <html lang="en"> | ||||
| 
 | ||||
| <head> | ||||
|     <meta charset="utf-8" /> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||||
|     <base href="/" /> | ||||
|     <link rel="stylesheet" href="app.css" /> | ||||
|     <link rel="stylesheet" href="AccessQueuePlayground.styles.css" /> | ||||
|     <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous"> | ||||
|     <link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" rel="stylesheet" /> | ||||
|     <link href="_content/Blazor.Bootstrap/blazor.bootstrap.css" rel="stylesheet" /> | ||||
|     <HeadOutlet @rendermode="InteractiveServer" /> | ||||
| </head> | ||||
| 
 | ||||
| <body> | ||||
|     <Routes @rendermode="InteractiveServer" /> | ||||
|     <script src="_framework/blazor.web.js"></script> | ||||
|     <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin="anonymous"></script> | ||||
|     <script src="_content/Blazor.Bootstrap/blazor.bootstrap.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; | ||||
|     } | ||||
|  | @ -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; | ||||
| } | ||||
|  | @ -0,0 +1,51 @@ | |||
| @page "/" | ||||
| @using AccessQueuePlayground.Models | ||||
| @using AccessQueuePlayground.Services | ||||
| @using BlazorBootstrap | ||||
| 
 | ||||
| @inject IAccessQueueManager Manager | ||||
| 
 | ||||
| <PageTitle>AccessQueue Playground</PageTitle> | ||||
| 
 | ||||
| <Button Color="ButtonColor.Success" @onclick="AddUser">Add User</Button> | ||||
| <Button Color="ButtonColor.Primary" @onclick="Refresh">Refresh</Button> | ||||
| 
 | ||||
| @if(Status != null) | ||||
| { | ||||
| 	@foreach(var user in Status.Users) | ||||
| 	{ | ||||
| 		<div> | ||||
| 			<p>@user.Id @user.LatestResponse?.HasAccess @user.LatestResponse?.ExpiresOn</p> | ||||
| 		</div> | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| @code { | ||||
| 	public AccessQueueStatus? Status; | ||||
| 
 | ||||
| 	protected override void OnInitialized() | ||||
| 	{ | ||||
| 		Manager.StatusUpdated += OnStatusUpdated; | ||||
| 		Status = Manager.GetStatus(); | ||||
| 	} | ||||
| 
 | ||||
| 	private void OnStatusUpdated() | ||||
| 	{ | ||||
| 		InvokeAsync(() => | ||||
| 		{ | ||||
| 			Status = Manager.GetStatus(); | ||||
| 			StateHasChanged(); | ||||
| 		}); | ||||
| 	} | ||||
| 
 | ||||
| 	public void Refresh() | ||||
| 	{ | ||||
| 		Status = Manager.GetStatus(); | ||||
| 	} | ||||
| 
 | ||||
| 	public void AddUser() | ||||
| 	{ | ||||
| 		Manager.AddUser(); | ||||
| 		Status = Manager.GetStatus(); | ||||
| 	} | ||||
| } | ||||
|  | @ -0,0 +1,6 @@ | |||
| <Router AppAssembly="typeof(Program).Assembly"> | ||||
|     <Found Context="routeData"> | ||||
|         <RouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)" /> | ||||
|         <FocusOnNavigate RouteData="routeData" Selector="h1" /> | ||||
|     </Found> | ||||
| </Router> | ||||
|  | @ -0,0 +1,10 @@ | |||
| @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 AccessQueuePlayground | ||||
| @using AccessQueuePlayground.Components | ||||
|  | @ -0,0 +1,12 @@ | |||
| using AccessQueueService.Models; | ||||
| 
 | ||||
| namespace AccessQueuePlayground.Models | ||||
| { | ||||
|     public class AccessQueueStatus | ||||
|     { | ||||
|         public List<User> Users { get; set; } = []; | ||||
|         public int QueueSize { get; set; } | ||||
|         public int ActiveTickets { get; set; } | ||||
|         public int UnexpiredTickets { get; set; } | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,11 @@ | |||
| using AccessQueueService.Models; | ||||
| 
 | ||||
| namespace AccessQueuePlayground.Models | ||||
| { | ||||
|     public class User | ||||
|     { | ||||
|         public Guid Id { get; set; } | ||||
|         public bool Active { get; set; } | ||||
|         public AccessResponse? LatestResponse { get; set; } | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,34 @@ | |||
| using AccessQueuePlayground.Components; | ||||
| using AccessQueuePlayground.Services; | ||||
| using AccessQueueService.Data; | ||||
| using AccessQueueService.Services; | ||||
| 
 | ||||
| var builder = WebApplication.CreateBuilder(args); | ||||
| 
 | ||||
| // Add services to the container. | ||||
| builder.Services.AddRazorComponents() | ||||
|     .AddInteractiveServerComponents(); | ||||
| builder.Services.AddSingleton<IAccessService, AccessService>(); | ||||
| builder.Services.AddSingleton<IAccessQueueRepo, DictionaryAccessQueueRepo>(); | ||||
| builder.Services.AddSingleton<IAccessQueueManager, AccessQueueManager>(); | ||||
| builder.Services.AddHostedService<AccessQueueBackgroundService>(); | ||||
| 
 | ||||
| var app = builder.Build(); | ||||
| 
 | ||||
| // Configure the HTTP request pipeline. | ||||
| if (!app.Environment.IsDevelopment()) | ||||
| { | ||||
|     app.UseExceptionHandler("/Error", createScopeForErrors: true); | ||||
|     // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. | ||||
|     app.UseHsts(); | ||||
| } | ||||
| 
 | ||||
| app.UseHttpsRedirection(); | ||||
| 
 | ||||
| app.UseStaticFiles(); | ||||
| app.UseAntiforgery(); | ||||
| 
 | ||||
| app.MapRazorComponents<App>() | ||||
|     .AddInteractiveServerRenderMode(); | ||||
| 
 | ||||
| app.Run(); | ||||
|  | @ -0,0 +1,38 @@ | |||
| { | ||||
|   "$schema": "http://json.schemastore.org/launchsettings.json", | ||||
|     "iisSettings": { | ||||
|       "windowsAuthentication": false, | ||||
|       "anonymousAuthentication": true, | ||||
|       "iisExpress": { | ||||
|         "applicationUrl": "http://localhost:25310", | ||||
|         "sslPort": 44353 | ||||
|       } | ||||
|     }, | ||||
|     "profiles": { | ||||
|       "http": { | ||||
|         "commandName": "Project", | ||||
|         "dotnetRunMessages": true, | ||||
|         "launchBrowser": true, | ||||
|         "applicationUrl": "http://localhost:5108", | ||||
|         "environmentVariables": { | ||||
|           "ASPNETCORE_ENVIRONMENT": "Development" | ||||
|         } | ||||
|       }, | ||||
|       "https": { | ||||
|         "commandName": "Project", | ||||
|         "dotnetRunMessages": true, | ||||
|         "launchBrowser": true, | ||||
|         "applicationUrl": "https://localhost:7211;http://localhost:5108", | ||||
|         "environmentVariables": { | ||||
|           "ASPNETCORE_ENVIRONMENT": "Development" | ||||
|         } | ||||
|       }, | ||||
|       "IIS Express": { | ||||
|         "commandName": "IISExpress", | ||||
|         "launchBrowser": true, | ||||
|         "environmentVariables": { | ||||
|           "ASPNETCORE_ENVIRONMENT": "Development" | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | @ -0,0 +1,25 @@ | |||
| using System.Threading; | ||||
| using System.Threading.Tasks; | ||||
| using Microsoft.Extensions.Hosting; | ||||
| 
 | ||||
| namespace AccessQueuePlayground.Services | ||||
| { | ||||
|     public class AccessQueueBackgroundService : BackgroundService | ||||
|     { | ||||
|         private readonly IAccessQueueManager _accessQueueManager; | ||||
| 
 | ||||
|         public AccessQueueBackgroundService(IAccessQueueManager accessQueueManager) | ||||
|         { | ||||
|             _accessQueueManager = accessQueueManager; | ||||
|         } | ||||
| 
 | ||||
|         protected override async Task ExecuteAsync(CancellationToken stoppingToken) | ||||
|         { | ||||
|             while (!stoppingToken.IsCancellationRequested) | ||||
|             { | ||||
|                 await _accessQueueManager.RecalculateStatus(); | ||||
|                 await Task.Delay(1000, stoppingToken); // Run every second | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,67 @@ | |||
| using System.Collections.Concurrent; | ||||
| using AccessQueuePlayground.Models; | ||||
| using AccessQueueService.Models; | ||||
| using AccessQueueService.Services; | ||||
| 
 | ||||
| namespace AccessQueuePlayground.Services | ||||
| { | ||||
|     public class AccessQueueManager : IAccessQueueManager | ||||
|     { | ||||
|         private readonly IAccessService _accessService; | ||||
|         private readonly ConcurrentDictionary<Guid, User> _users; | ||||
|         private AccessQueueStatus _status; | ||||
|         public event Action? StatusUpdated; | ||||
| 
 | ||||
|         private void NotifyStatusUpdated() | ||||
|         { | ||||
|             StatusUpdated?.Invoke(); | ||||
|         } | ||||
| 
 | ||||
|         public AccessQueueManager(IAccessService accessService) | ||||
|         { | ||||
|             _accessService = accessService; | ||||
|             _users = new ConcurrentDictionary<Guid, User>(); | ||||
|             _status = new AccessQueueStatus(); | ||||
|         } | ||||
| 
 | ||||
|         public AccessQueueStatus GetStatus() => _status; | ||||
| 
 | ||||
|         public Guid AddUser() | ||||
|         { | ||||
|             var id = Guid.NewGuid(); | ||||
|             _users[id] = new User | ||||
|             { | ||||
|                 Id = id, | ||||
|                 Active = true, | ||||
|             }; | ||||
|             return id; | ||||
|         } | ||||
| 
 | ||||
|         public void ToggleUserActivity(Guid userId) | ||||
|         { | ||||
|             var user = _users[userId]; | ||||
|             if (user != null) | ||||
|             { | ||||
|                 user.Active = !user.Active; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public async Task RecalculateStatus() | ||||
|         { | ||||
|             var userList = _users.Values.ToList(); | ||||
|             var newStatus = new AccessQueueStatus(); | ||||
|             foreach (var user in userList) | ||||
|             { | ||||
|                 AccessResponse? response = user.LatestResponse; | ||||
|                 if (user.Active) | ||||
|                 { | ||||
|                     response = await _accessService.RequestAccess(user.Id); | ||||
|                     user.LatestResponse = response; | ||||
|                 } | ||||
|                 newStatus.Users.Add(user); | ||||
|             } | ||||
|             _status = newStatus; | ||||
|             NotifyStatusUpdated(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,14 @@ | |||
| using AccessQueuePlayground.Models; | ||||
| 
 | ||||
| namespace AccessQueuePlayground.Services | ||||
| { | ||||
|     public interface IAccessQueueManager | ||||
|     { | ||||
|         public event Action? StatusUpdated; | ||||
|         public Task RecalculateStatus(); | ||||
|         public AccessQueueStatus GetStatus(); | ||||
|         public Guid AddUser(); | ||||
|         public void ToggleUserActivity(Guid userId); | ||||
| 
 | ||||
|     } | ||||
| } | ||||
|  | @ -0,0 +1,8 @@ | |||
| { | ||||
|   "Logging": { | ||||
|     "LogLevel": { | ||||
|       "Default": "Information", | ||||
|       "Microsoft.AspNetCore": "Warning" | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | @ -0,0 +1,15 @@ | |||
| { | ||||
|     "Logging": { | ||||
|         "LogLevel": { | ||||
|             "Default": "Information", | ||||
|             "Microsoft.AspNetCore": "Warning" | ||||
|         } | ||||
|     }, | ||||
|     "AccessQueue": { | ||||
|         "CapacityLimit": 10, // Maximum number of active users | ||||
|         "ActivitySeconds": 5, // Time since last access before a user is considered inactive | ||||
|         "ExpirationSeconds": 30, // 12 hours - Time before a user access is revoked | ||||
|         "RollingExpiration": true // Whether to extend expiration time on access | ||||
|     }, | ||||
|     "AllowedHosts": "*" | ||||
| } | ||||
|  | @ -0,0 +1,29 @@ | |||
| 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,6 +7,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AccessQueueService", "Acces | |||
| EndProject | ||||
| Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AccessQueueServiceTests", "AccessQueueServiceTests\AccessQueueServiceTests.csproj", "{1DF48A19-A2B3-4B0C-B726-E65B8E023760}" | ||||
| EndProject | ||||
| Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AccessQueuePlayground", "AccessQueuePlayground\AccessQueuePlayground.csproj", "{65D5E841-7B02-4A55-89C6-12082FA1BCAF}" | ||||
| EndProject | ||||
| Global | ||||
| 	GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||||
| 		Debug|Any CPU = Debug|Any CPU | ||||
|  | @ -21,6 +23,10 @@ Global | |||
| 		{1DF48A19-A2B3-4B0C-B726-E65B8E023760}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{1DF48A19-A2B3-4B0C-B726-E65B8E023760}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{1DF48A19-A2B3-4B0C-B726-E65B8E023760}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 		{65D5E841-7B02-4A55-89C6-12082FA1BCAF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||||
| 		{65D5E841-7B02-4A55-89C6-12082FA1BCAF}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||||
| 		{65D5E841-7B02-4A55-89C6-12082FA1BCAF}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||||
| 		{65D5E841-7B02-4A55-89C6-12082FA1BCAF}.Release|Any CPU.Build.0 = Release|Any CPU | ||||
| 	EndGlobalSection | ||||
| 	GlobalSection(SolutionProperties) = preSolution | ||||
| 		HideSolutionNode = FALSE | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue