From 687b4f7a49645382634d22b1059aadf74036c9bd Mon Sep 17 00:00:00 2001 From: Jesse Date: Thu, 2 Oct 2025 21:03:27 +0200 Subject: [PATCH] Add blazor & MVC Support (#602) * Add initial code for Razor pages support * Remove finalizer * Try fully loading blazor This is most likely entirely broken because of a rebase now * UseSptBlazor after app.Use * Fix up StaticWebAsset loading, add MudBlazor * Implement page * Update comment * Replaced existing status page with razor * Track background video in LFS * Update attributes * Improved status page theming * Fix up wwwroot publish folder to SPT_Data/wwwroot * Added name to page * Remove unnecessary code * Begin fixing up MVC & Blazor for modding * Update TestMod * Cleanup todo * Further work out mod support * Re-order initialization and use logger * Rename library to SPTarkov.Server.Web --------- Co-authored-by: Chomp Co-authored-by: Chomp <27521899+chompDev@users.noreply.github.com> --- .gitattributes | 1 + .../SPTarkov.Server.Core/Status/StatusPage.cs | 55 --- .../Utils/Logger/SptLogger.cs | 5 - .../SPTarkov.Server.Web/Components/App.razor | 19 + .../Components/Layout/BaseMainLayout.razor | 11 + .../Layout/BaseMudBlazorLayout.razor | 20 + .../Components/Pages/ExamplePage.razor | 27 ++ .../Components/Pages/Status.razor | 110 +++++ .../Components/Pages/ThankYou.razor | 418 ++++++++++++++++++ .../Components/Routes.razor | 12 + .../Components/_Imports.razor | 8 + .../SPTarkov.Server.Web/IModBlazorMetadata.cs | 22 + Libraries/SPTarkov.Server.Web/SPTWeb.cs | 76 ++++ .../SPTarkov.Server.Web.csproj | 29 ++ .../wwwroot/thank-you/background.mp4 | 3 + SPTarkov.Server/Program.cs | 12 +- SPTarkov.Server/SPTarkov.Server.csproj | 5 +- .../Components/Pages/TestModPage.razor | 9 + Testing/TestMod/Controllers/TestController.cs | 12 + Testing/TestMod/TestMod.cs | 5 +- Testing/TestMod/TestMod.csproj | 22 +- Testing/TestMod/wwwroot/chomp.jpg | Bin 0 -> 25907 bytes server-csharp.sln | 12 +- 23 files changed, 820 insertions(+), 73 deletions(-) delete mode 100644 Libraries/SPTarkov.Server.Core/Status/StatusPage.cs create mode 100644 Libraries/SPTarkov.Server.Web/Components/App.razor create mode 100644 Libraries/SPTarkov.Server.Web/Components/Layout/BaseMainLayout.razor create mode 100644 Libraries/SPTarkov.Server.Web/Components/Layout/BaseMudBlazorLayout.razor create mode 100644 Libraries/SPTarkov.Server.Web/Components/Pages/ExamplePage.razor create mode 100644 Libraries/SPTarkov.Server.Web/Components/Pages/Status.razor create mode 100644 Libraries/SPTarkov.Server.Web/Components/Pages/ThankYou.razor create mode 100644 Libraries/SPTarkov.Server.Web/Components/Routes.razor create mode 100644 Libraries/SPTarkov.Server.Web/Components/_Imports.razor create mode 100644 Libraries/SPTarkov.Server.Web/IModBlazorMetadata.cs create mode 100644 Libraries/SPTarkov.Server.Web/SPTWeb.cs create mode 100644 Libraries/SPTarkov.Server.Web/SPTarkov.Server.Web.csproj create mode 100644 Libraries/SPTarkov.Server.Web/wwwroot/thank-you/background.mp4 create mode 100644 Testing/TestMod/Components/Pages/TestModPage.razor create mode 100644 Testing/TestMod/Controllers/TestController.cs create mode 100644 Testing/TestMod/wwwroot/chomp.jpg diff --git a/.gitattributes b/.gitattributes index c05dc58d..bc0bf399 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3,3 +3,4 @@ # LFS tracking for large JSON files and all images Libraries/SPTarkov.Server.Assets/SPT_Data/database/locations/**/looseLoot.json filter=lfs diff=lfs merge=lfs -text Libraries/SPTarkov.Server.Assets/SPT_Data/database/templates/items.json filter=lfs diff=lfs merge=lfs -text +Libraries/SPTarkov.Server.Web/wwwroot/thank-you/background.mp4 filter=lfs diff=lfs merge=lfs -text diff --git a/Libraries/SPTarkov.Server.Core/Status/StatusPage.cs b/Libraries/SPTarkov.Server.Core/Status/StatusPage.cs deleted file mode 100644 index 626f802e..00000000 --- a/Libraries/SPTarkov.Server.Core/Status/StatusPage.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System.Text; -using Microsoft.AspNetCore.Http; -using SPTarkov.DI.Annotations; -using SPTarkov.Server.Core.Models.Common; -using SPTarkov.Server.Core.Models.Spt.Config; -using SPTarkov.Server.Core.Servers; -using SPTarkov.Server.Core.Servers.Http; -using SPTarkov.Server.Core.Services; -using SPTarkov.Server.Core.Utils; - -namespace SPTarkov.Server.Core.Status; - -[Injectable(TypePriority = 0)] -public class StatusPage(TimeUtil timeUtil, ProfileActivityService profileActivityService, ConfigServer configServer) : IHttpListener -{ - protected readonly CoreConfig CoreConfig = configServer.GetConfig(); - - public bool CanHandle(MongoId sessionId, HttpContext context) - { - return context.Request.Method == "GET" && context.Request.Path.Value.Contains("/status"); - } - - public async Task Handle(MongoId sessionId, HttpContext context) - { - var resp = context.Response; - - var sptVersion = $"SPT version: {ProgramStatics.SPT_VERSION()}"; - var debugEnabled = $"Debug enabled: {ProgramStatics.DEBUG()}"; - var modsEnabled = $"Mods enabled: {ProgramStatics.MODS()}"; - var timeStarted = $"Started : {timeUtil.GetDateTimeFromTimeStamp(CoreConfig.ServerStartTime.Value)}"; - var uptime = $"Uptime: {DateTimeOffset.UtcNow.ToUnixTimeSeconds() - CoreConfig.ServerStartTime} seconds".ToArray(); - var activeProfiles = profileActivityService.GetActiveProfileIdsWithinMinutes(30); - var activePlayerCount = $"Profiles active in last 30 minutes: {activeProfiles.Count}. {string.Join(",", activeProfiles)}"; - - resp.StatusCode = 200; - resp.ContentType = "text/html"; - - await resp.Body.WriteAsync(Encoding.ASCII.GetBytes(sptVersion)); - await resp.Body.WriteAsync(Encoding.ASCII.GetBytes("
")); - await resp.Body.WriteAsync(Encoding.ASCII.GetBytes(debugEnabled)); - await resp.Body.WriteAsync(Encoding.ASCII.GetBytes("
")); - await resp.Body.WriteAsync(Encoding.ASCII.GetBytes(modsEnabled)); - await resp.Body.WriteAsync(Encoding.ASCII.GetBytes("
")); - - await resp.Body.WriteAsync(Encoding.ASCII.GetBytes(timeStarted)); - await resp.Body.WriteAsync(Encoding.ASCII.GetBytes("
")); - await resp.Body.WriteAsync(Encoding.ASCII.GetBytes(uptime)); - await resp.Body.WriteAsync(Encoding.ASCII.GetBytes("
")); - await resp.Body.WriteAsync(Encoding.ASCII.GetBytes(activePlayerCount)); - await resp.Body.WriteAsync(Encoding.ASCII.GetBytes("
")); - - await resp.StartAsync(); - await resp.CompleteAsync(); - } -} diff --git a/Libraries/SPTarkov.Server.Core/Utils/Logger/SptLogger.cs b/Libraries/SPTarkov.Server.Core/Utils/Logger/SptLogger.cs index fa9be5ed..ef16e795 100644 --- a/Libraries/SPTarkov.Server.Core/Utils/Logger/SptLogger.cs +++ b/Libraries/SPTarkov.Server.Core/Utils/Logger/SptLogger.cs @@ -15,11 +15,6 @@ public class SptLogger : ISptLogger, IDisposable private const string ConfigurationPathDev = "./sptLogger.Development.json"; private SptLoggerConfiguration _config; - ~SptLogger() - { - _loggerQueueManager.DumpAndStop(); - } - public SptLogger(FileUtil fileUtil, JsonUtil jsonUtil, SptLoggerQueueManager loggerQueueManager) { _category = typeof(T).FullName; diff --git a/Libraries/SPTarkov.Server.Web/Components/App.razor b/Libraries/SPTarkov.Server.Web/Components/App.razor new file mode 100644 index 00000000..35e0c7a9 --- /dev/null +++ b/Libraries/SPTarkov.Server.Web/Components/App.razor @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + +@code { +} diff --git a/Libraries/SPTarkov.Server.Web/Components/Layout/BaseMainLayout.razor b/Libraries/SPTarkov.Server.Web/Components/Layout/BaseMainLayout.razor new file mode 100644 index 00000000..af237b4e --- /dev/null +++ b/Libraries/SPTarkov.Server.Web/Components/Layout/BaseMainLayout.razor @@ -0,0 +1,11 @@ +@inherits LayoutComponentBase + +
+
+ @Body +
+
+ +@code { + +} diff --git a/Libraries/SPTarkov.Server.Web/Components/Layout/BaseMudBlazorLayout.razor b/Libraries/SPTarkov.Server.Web/Components/Layout/BaseMudBlazorLayout.razor new file mode 100644 index 00000000..33f8eb3a --- /dev/null +++ b/Libraries/SPTarkov.Server.Web/Components/Layout/BaseMudBlazorLayout.razor @@ -0,0 +1,20 @@ +@inherits LayoutComponentBase + + + + + + + + + + + +
+@Body + +
+ +@code { + +} diff --git a/Libraries/SPTarkov.Server.Web/Components/Pages/ExamplePage.razor b/Libraries/SPTarkov.Server.Web/Components/Pages/ExamplePage.razor new file mode 100644 index 00000000..58b71289 --- /dev/null +++ b/Libraries/SPTarkov.Server.Web/Components/Pages/ExamplePage.razor @@ -0,0 +1,27 @@ +@page "/example-page" +@using SPTarkov.Server.Web.Components.Layout +@layout BaseMudBlazorLayout + + + + + + + + SPT 4.0 + + + +
+ Is pretty awesome +
+ + Feature 1 + Feature 2 + Feature 3 + +
+
+
+
+
diff --git a/Libraries/SPTarkov.Server.Web/Components/Pages/Status.razor b/Libraries/SPTarkov.Server.Web/Components/Pages/Status.razor new file mode 100644 index 00000000..211f9ed8 --- /dev/null +++ b/Libraries/SPTarkov.Server.Web/Components/Pages/Status.razor @@ -0,0 +1,110 @@ +@page "/status" +@using SPTarkov.Server.Core.Helpers +@using SPTarkov.Server.Core.Models.Spt.Config +@using SPTarkov.Server.Core.Servers +@using SPTarkov.Server.Core.Services +@using SPTarkov.Server.Core.Utils +@using SPTarkov.Server.Web.Components.Layout + +@inject ConfigServer ConfigServer +@inject TimeUtil TimeUtil +@inject ProfileActivityService ProfileActivityService +@inject ProfileHelper ProfileHelper + +@layout BaseMudBlazorLayout + + + + + + + + + + + + + + + Server health + + + + + SPT Version: @_sptVersion + Mods @(_modsEnabled ? "ENABLED" : "DISABLED") + Debug @(_debugEnabled ? "ENABLED" : "DISABLED") + Time started: @_startTime + Uptime: @_uptimeSeconds seconds + Total profile count: @_totalProfileCount + + + @foreach (var profile in _activeProfiles) + { + + @profile + + } + + + + + + + + + +@code +{ + private string _sptVersion = string.Empty; + private bool _debugEnabled = false; + private bool _modsEnabled = false; + private DateTime _startTime = DateTime.Now; + private long _uptimeSeconds = 0; + private readonly List _activeProfiles = []; + private int _totalProfileCount = 0; + + protected override void OnInitialized() + { + base.OnInitialized(); + + var coreConfig = ConfigServer.GetConfig(); + + _sptVersion = ProgramStatics.SPT_VERSION().ToString(); + _debugEnabled = ProgramStatics.DEBUG(); + _modsEnabled = ProgramStatics.MODS(); + _startTime = TimeUtil.GetDateTimeFromTimeStamp(coreConfig.ServerStartTime.Value); + _uptimeSeconds = DateTimeOffset.UtcNow.ToUnixTimeSeconds() - coreConfig.ServerStartTime.Value; + + var activeProfileIds = ProfileActivityService.GetActiveProfileIdsWithinMinutes(30); + if (activeProfileIds.Count == 0) + { + _activeProfiles.Add("None"); + } + else + { + foreach (var activeProfileId in activeProfileIds) + { + _activeProfiles.Add($"{activeProfileId} ({ProfileHelper.GetPmcProfile(activeProfileId).Info.Nickname})"); + } + } + + _totalProfileCount = ProfileHelper.GetProfiles().Count; + } + + private readonly MudTheme _theme = new() + { + PaletteDark = new PaletteDark + { + Primary = Colors.Blue.Default, + Secondary = Colors.Orange.Default, + Warning = Colors.Amber.Default, + Info = Colors.Blue.Lighten1, + Success = Colors.Green.Default, + Background = "rgba(0,0,0,0.9)", + Surface = "rgba(255,255,255,0.05)", + AppbarBackground = "rgba(0,0,0,0.8)", + TextPrimary = "#FFFFFF" + } + }; +} diff --git a/Libraries/SPTarkov.Server.Web/Components/Pages/ThankYou.razor b/Libraries/SPTarkov.Server.Web/Components/Pages/ThankYou.razor new file mode 100644 index 00000000..e82c36b2 --- /dev/null +++ b/Libraries/SPTarkov.Server.Web/Components/Pages/ThankYou.razor @@ -0,0 +1,418 @@ +@page "/" +@using Microsoft.JSInterop +@using MudBlazor +@using SPTarkov.Server.Web.Components.Layout +@inject IJSRuntime JSRuntime +Thank You - Single Player Tarkov - Version 4 Contributors + + + + + + + + +
+ +
+
+ + + + + + + + Thank You + + + Version 4 Contributors + + + + + + The release of Single Player Tarkov Version 4 would not have been possible without the incredible dedication, talent, and passion of our amazing community. From core developers to modders, testers to documentation writers, each person listed here has contributed to making SPT what it is today. + + + + To everyone who spent countless hours debugging, coding, testing, and supporting the project, this is for you. Your work has transformed the way thousands experience Tarkov, and we are forever grateful. + + + + + + @foreach (var contributor in ShuffledContributors) + { + + + + + @contributor + + + + + } + + + + + Every commit, every bug report, every line of code, and every moment of support has shaped this project. You are the heart of Single Player Tarkov. + + + + Patreon Supporters + + + + + A special thank you to our generous Patreon supporters whose financial contributions have helped sustain and grow the SPT project. Your support enables us to maintain infrastructure, develop new features, and keep this project alive for everyone to enjoy. + + + + The following list includes supporters who opted to be publicly recognized. To all our supporters not listed here, whether by choice or oversight, please know that your contributions are equally valued and deeply appreciated. Every single patron, visible or not, makes this project possible. ♥️ + + + + + + @foreach (var supporter in ShuffledPatreonSupporters) + { + + + + + + + @supporter + + + + + + } + + + + + + With deepest gratitude,
+ The SPT Team +
+
+
+
+
+
+ + + + The SPT project is not affiliated with Battlestate Games Ltd. in any way. All trademarks are property of their respective owners. + + + + + + +@code { + private readonly List Contributors = new() + { + "5o2", "AdmiralAwsum", "Angel-git", "ArchangelWTF", "BrotherVeren", + "CameronW1", "CJ", "Clodan", "CP89", "CWX", "DanW", "devmaximum", + "Doup22", "DrakiaXYZ", "fearthedje", "GentlemenSausage", "Guidot42", + "HB53", "Hulkhan22", "January", "Kaeno", "Lacyway", "Muramas", + "nailz420", "Parataku", "ParanoiaBruce", "PhantomInTime", "qe201020335", + "R3ality", "Razzmatazz", "Redbeard", "Refringe", "ShadowXtrex", + "Shibdib", "Stealthsuit", "studentchy", "Tetris", "Th3NightHawk", + "TheHeadPhonesGuy", "ThyMuffinMan", "ultragastro", "Valens", + "XeonDead", "yurikus", "Chilly" + }; + + private readonly List PatreonSupporters = new() + { + "Refringe", "DrakiaXYZ", "Tron", "Bepis", "John Thicc", + "Cardsmen", "Nexstat", "NumberedJester", "Irabeth Tyrabade", "DanW" + }; + + private List ShuffledContributors = new(); + private List ShuffledPatreonSupporters = new(); + + private readonly MudTheme _theme = new() + { + PaletteDark = new PaletteDark() + { + Primary = Colors.Blue.Default, + Secondary = Colors.Orange.Default, + Warning = Colors.Amber.Default, + Info = Colors.Blue.Lighten1, + Success = Colors.Green.Default, + Background = "rgba(0,0,0,0.9)", + Surface = "rgba(255,255,255,0.05)", + AppbarBackground = "rgba(0,0,0,0.8)", + TextPrimary = "#FFFFFF" + } + }; + + protected override void OnInitialized() + { + ShuffledContributors = ShuffleList(Contributors); + ShuffledPatreonSupporters = ShuffleList(PatreonSupporters); + } + + private async Task TriggerConfetti(bool isPatreon, string name) + { + //Todo: Needs implemnetation + + if (isPatreon) + { + } + else + { + } + } + + private static List ShuffleList(List list) + { + var shuffled = new List(list); + + for (int i = shuffled.Count - 1; i > 0; i--) + { + int j = Random.Shared.Next(i + 1); + (shuffled[i], shuffled[j]) = (shuffled[j], shuffled[i]); + } + + return shuffled; + } +} diff --git a/Libraries/SPTarkov.Server.Web/Components/Routes.razor b/Libraries/SPTarkov.Server.Web/Components/Routes.razor new file mode 100644 index 00000000..de83bf4c --- /dev/null +++ b/Libraries/SPTarkov.Server.Web/Components/Routes.razor @@ -0,0 +1,12 @@ +@using System.Reflection +@using SPTarkov.Server.Web.Components.Layout + + + + + + + + +@code { +} diff --git a/Libraries/SPTarkov.Server.Web/Components/_Imports.razor b/Libraries/SPTarkov.Server.Web/Components/_Imports.razor new file mode 100644 index 00000000..75f99676 --- /dev/null +++ b/Libraries/SPTarkov.Server.Web/Components/_Imports.razor @@ -0,0 +1,8 @@ +@using Microsoft.AspNetCore.Authorization +@using Microsoft.AspNetCore.Components.Authorization +@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 MudBlazor; diff --git a/Libraries/SPTarkov.Server.Web/IModBlazorMetadata.cs b/Libraries/SPTarkov.Server.Web/IModBlazorMetadata.cs new file mode 100644 index 00000000..eff53623 --- /dev/null +++ b/Libraries/SPTarkov.Server.Web/IModBlazorMetadata.cs @@ -0,0 +1,22 @@ +namespace SPTarkov.Server.Web; + +/// +/// This empty interface is used as a metadata marker to identify mod assemblies that integrate with Blazor or MVC. +/// +/// +/// Implementing this interface signals to the host application to: +/// +/// +/// Link the mod's wwwroot directory, enabling serving of static web assets (CSS, JS, etc.). +/// +/// +/// Register the mod's Blazor components and pages for routing within the application. +/// +/// +/// Register the mod's MVC controllers for use as APIs where necessary. +/// +/// +/// +/// This interface is intentionally empty but may be extended in the future to include additional metadata. +/// +public interface IModWebMetadata { } diff --git a/Libraries/SPTarkov.Server.Web/SPTWeb.cs b/Libraries/SPTarkov.Server.Web/SPTWeb.cs new file mode 100644 index 00000000..afc034c6 --- /dev/null +++ b/Libraries/SPTarkov.Server.Web/SPTWeb.cs @@ -0,0 +1,76 @@ +using Microsoft.Extensions.FileProviders; +using MudBlazor.Services; +using SPTarkov.Server.Core.Models.Spt.Mod; +using SPTarkov.Server.Web.Components; + +namespace SPTarkov.Server.Web; + +public static class SPTWeb +{ + internal static IEnumerable SptWebMods = []; + + public static void InitializeSptBlazor(this WebApplicationBuilder builder, IReadOnlyList sptMods) + { + SptWebMods = sptMods.Where(mod => mod.ModMetadata is IModWebMetadata).ToList(); + + builder.WebHost.UseStaticWebAssets(); + builder.Services.AddMudServices(); + builder.Services.AddRazorComponents().AddInteractiveServerComponents(); + + var mvcBuilder = builder.Services.AddControllers(); + + foreach (var assembly in SptWebMods.SelectMany(mod => mod.Assemblies)) + { + mvcBuilder.AddApplicationPart(assembly); + } + } + + public static void UseSptBlazor(this WebApplication app) + { + var logger = app.Services.GetRequiredService>(); + + app.UseAntiforgery(); + +#if DEBUG + //MS currently has a bug where streaming video doesn't work properly in debug, unless you use this + //Issue: https://github.com/dotnet/aspnetcore/issues/63320 + app.UseStaticFiles(); +#else + app.MapStaticAssets(); +#endif + var razorBuilder = app.MapRazorComponents().AddInteractiveServerRenderMode(); + + foreach (var mod in SptWebMods) + { + foreach (var assembly in mod.Assemblies) + { + razorBuilder.AddAdditionalAssemblies(assembly); + } + + var modAssembly = mod.ModMetadata.GetType().Assembly; + + var location = Path.GetDirectoryName(modAssembly.Location); + + if (!string.IsNullOrEmpty(location) && Directory.Exists(Path.Combine(location, "wwwroot"))) + { + var modAssemblyName = modAssembly.GetName().Name; + + logger.LogDebug( + "Mod {modName} has a wwwroot, mapping to /{modAssemblyName}/", + mod.ModMetadata.Name, + modAssembly.GetName().Name + ); + + app.UseStaticFiles( + new StaticFileOptions + { + FileProvider = new PhysicalFileProvider(Path.Combine(location, "wwwroot")), + RequestPath = $"/{modAssembly.GetName().Name}", + } + ); + } + } + + app.MapControllers(); + } +} diff --git a/Libraries/SPTarkov.Server.Web/SPTarkov.Server.Web.csproj b/Libraries/SPTarkov.Server.Web/SPTarkov.Server.Web.csproj new file mode 100644 index 00000000..e9fbf1cb --- /dev/null +++ b/Libraries/SPTarkov.Server.Web/SPTarkov.Server.Web.csproj @@ -0,0 +1,29 @@ + + + + SPTarkov.Server.Web + Single Player Tarkov + Common shared library for the Single Player Tarkov projects. + Copyright (c) Single Player Tarkov 2025 + LICENSE + https://sp-tarkov.com + https://github.com/sp-tarkov/server-csharp + enable + Library + true + true + true + + true + SPTarkov.Server.Web + + + + + + + + + + + diff --git a/Libraries/SPTarkov.Server.Web/wwwroot/thank-you/background.mp4 b/Libraries/SPTarkov.Server.Web/wwwroot/thank-you/background.mp4 new file mode 100644 index 00000000..8315c16b --- /dev/null +++ b/Libraries/SPTarkov.Server.Web/wwwroot/thank-you/background.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7532edb85d6c446c38d9e7e7a7e2f31544ba62ab502381396c86bf9934cd4db8 +size 61186672 diff --git a/SPTarkov.Server/Program.cs b/SPTarkov.Server/Program.cs index a118cdc6..34bb9893 100644 --- a/SPTarkov.Server/Program.cs +++ b/SPTarkov.Server/Program.cs @@ -1,5 +1,6 @@ using System.Net; using System.Net.Sockets; +using System.Reflection; using System.Runtime.InteropServices; using System.Security.Authentication; using System.Text; @@ -20,6 +21,7 @@ using SPTarkov.Server.Core.Utils.Logger; using SPTarkov.Server.Logger; using SPTarkov.Server.Modding; using SPTarkov.Server.Services; +using SPTarkov.Server.Web; namespace SPTarkov.Server; @@ -41,7 +43,7 @@ public static class Program ProgramStatics.Initialize(); // Create web builder and logger - var builder = CreateNewHostBuilder(args); + var builder = CreateNewHostBuilder(); var diHandler = new DependencyInjectionHandler(builder.Services); // register SPT components @@ -64,6 +66,8 @@ public static class Program } diHandler.InjectAll(); + builder.InitializeSptBlazor(loadedMods); + builder.Services.AddSingleton(builder); builder.Services.AddSingleton>(loadedMods); // Configure Kestrel options @@ -125,6 +129,8 @@ public static class Program await context.RequestServices.GetRequiredService().HandleRequest(context, next); } ); + + app.UseSptBlazor(); } private static void ConfigureKestrel(WebApplicationBuilder builder) @@ -167,9 +173,9 @@ public static class Program ); } - private static WebApplicationBuilder CreateNewHostBuilder(string[]? args = null) + private static WebApplicationBuilder CreateNewHostBuilder() { - var builder = WebApplication.CreateBuilder(args); + var builder = WebApplication.CreateBuilder(new WebApplicationOptions { WebRootPath = "./SPT_Data/wwwroot" }); builder.Logging.ClearProviders(); builder.Configuration.SetBasePath(Directory.GetCurrentDirectory()); builder.Host.UseSptLogger(); diff --git a/SPTarkov.Server/SPTarkov.Server.csproj b/SPTarkov.Server/SPTarkov.Server.csproj index 8255f5e5..1fc74be7 100644 --- a/SPTarkov.Server/SPTarkov.Server.csproj +++ b/SPTarkov.Server/SPTarkov.Server.csproj @@ -14,8 +14,10 @@ Exe true SPT.Server - false true + + true + ../SPT_Data/wwwroot/ ..\Libraries\SPTarkov.Server.Assets\SPT_Data\images\icon.ico @@ -27,6 +29,7 @@ + diff --git a/Testing/TestMod/Components/Pages/TestModPage.razor b/Testing/TestMod/Components/Pages/TestModPage.razor new file mode 100644 index 00000000..16584ae3 --- /dev/null +++ b/Testing/TestMod/Components/Pages/TestModPage.razor @@ -0,0 +1,9 @@ +@page "/test/page" + +

TestModPage

+ +Chomp! + +@code { + +} diff --git a/Testing/TestMod/Controllers/TestController.cs b/Testing/TestMod/Controllers/TestController.cs new file mode 100644 index 00000000..6f3b974e --- /dev/null +++ b/Testing/TestMod/Controllers/TestController.cs @@ -0,0 +1,12 @@ +using Microsoft.AspNetCore.Mvc; + +namespace TestMod.Controllers; + +public class TestController : Controller +{ + [HttpGet("/test/ping")] + public IActionResult Ping() + { + return Content("Pong from MVC!"); + } +} diff --git a/Testing/TestMod/TestMod.cs b/Testing/TestMod/TestMod.cs index ef600a54..46e7044c 100644 --- a/Testing/TestMod/TestMod.cs +++ b/Testing/TestMod/TestMod.cs @@ -2,11 +2,12 @@ using SPTarkov.Server.Core.DI; using SPTarkov.Server.Core.Models.Spt.Mod; using SPTarkov.Server.Core.Models.Utils; +using SPTarkov.Server.Web; using Version = SemanticVersioning.Version; namespace TestMod; -public record TestModMetadata : AbstractModMetadata +public record TestModMetadata : AbstractModMetadata, IModWebMetadata { public override string ModGuid { get; init; } = "com.sp-tarkov.test-mod"; public override string Name { get; init; } = "test-mod"; @@ -26,6 +27,8 @@ public class TestMod(ISptLogger logger) : IOnLoad { public Task OnLoad() { + logger.Info("Test mod loaded!"); + return Task.CompletedTask; } } diff --git a/Testing/TestMod/TestMod.csproj b/Testing/TestMod/TestMod.csproj index 459a5d64..8f3807e9 100644 --- a/Testing/TestMod/TestMod.csproj +++ b/Testing/TestMod/TestMod.csproj @@ -1,8 +1,9 @@ - + enable - TestMod2 + TestMod + Library @@ -10,20 +11,27 @@ + + + + + - + + + diff --git a/Testing/TestMod/wwwroot/chomp.jpg b/Testing/TestMod/wwwroot/chomp.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e3cf4fa580182f8eed35cfda0de6314e937ce76b GIT binary patch literal 25907 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 literal 0 HcmV?d00001 diff --git a/server-csharp.sln b/server-csharp.sln index 52d9958b..5905d812 100644 --- a/server-csharp.sln +++ b/server-csharp.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 -VisualStudioVersion = 17.12.35527.113 d17.12 +VisualStudioVersion = 17.12.35527.113 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SPTarkov.Server", "SPTarkov.Server\SPTarkov.Server.csproj", "{1F5ED9C6-8B1F-4776-85AB-B387CBBC5557}" EndProject @@ -43,6 +43,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestMod", "Testing\TestMod\ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ceciler.JsonExtensionData", "Patches\Ceciler.JsonExtensionData\Ceciler.JsonExtensionData.csproj", "{5D09182A-B0B3-406C-AE88-EE0929F9260C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SPTarkov.Server.Web", "Libraries\SPTarkov.Server.Web\SPTarkov.Server.Web.csproj", "{BB1EB56E-9D40-8497-5A6D-B2E35E83FA89}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -105,6 +107,10 @@ Global {5D09182A-B0B3-406C-AE88-EE0929F9260C}.Debug|Any CPU.Build.0 = Debug|Any CPU {5D09182A-B0B3-406C-AE88-EE0929F9260C}.Release|Any CPU.ActiveCfg = Release|Any CPU {5D09182A-B0B3-406C-AE88-EE0929F9260C}.Release|Any CPU.Build.0 = Release|Any CPU + {BB1EB56E-9D40-8497-5A6D-B2E35E83FA89}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BB1EB56E-9D40-8497-5A6D-B2E35E83FA89}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BB1EB56E-9D40-8497-5A6D-B2E35E83FA89}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BB1EB56E-9D40-8497-5A6D-B2E35E83FA89}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -123,5 +129,9 @@ Global {28B90486-1436-4CD7-88D0-122B6963AB58} = {07B50C44-6D38-474E-87AF-68672D241EEB} {755E473C-14F2-40BC-9377-2FAB11CA91DC} = {07B50C44-6D38-474E-87AF-68672D241EEB} {5D09182A-B0B3-406C-AE88-EE0929F9260C} = {9E41CD5A-271C-4294-AAF9-8EB379311416} + {BB1EB56E-9D40-8497-5A6D-B2E35E83FA89} = {F084DDFD-89F3-44F9-89C3-5CA11F4CDEEF} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {6F730FC0-94A8-40B8-8E0E-5D6558E8422A} EndGlobalSection EndGlobal