mirror of
https://github.com/CommonLoon102/NBloodServerSupervisor.git
synced 2024-12-22 10:42:54 +01:00
add option to request private servers (#7)
This commit is contained in:
parent
c1b385c41d
commit
90ed231088
@ -1,10 +1,9 @@
|
||||
using Model;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
|
||||
namespace Common
|
||||
namespace Model
|
||||
{
|
||||
public class SpawnedServerInfo
|
||||
{
|
@ -16,7 +16,7 @@ namespace WebInterface.Controllers
|
||||
_stateService = stateService;
|
||||
}
|
||||
|
||||
[Route("nblood/home")]
|
||||
[Route("nblood/home", Name = "Home")]
|
||||
public IActionResult Index()
|
||||
{
|
||||
var servers = _stateService.ListServers(HttpContext.Request.Host.Host).Servers;
|
||||
|
@ -24,16 +24,18 @@ namespace WebInterface.Controllers
|
||||
|
||||
private readonly ILogger<NBloodController> _logger;
|
||||
private readonly IConfiguration _config;
|
||||
private readonly IStateService _listServersService;
|
||||
private readonly IStateService _stateService;
|
||||
private readonly IPrivateServerService _privateServerService;
|
||||
|
||||
private static readonly Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
|
||||
private static readonly IPEndPoint webApiListenerEndPoint = new IPEndPoint(IPAddress.Loopback, 11028);
|
||||
|
||||
public NBloodController(ILogger<NBloodController> logger, IConfiguration config, IStateService listServersService)
|
||||
public NBloodController(ILogger<NBloodController> logger,
|
||||
IConfiguration config,
|
||||
IStateService stateService,
|
||||
IPrivateServerService privateServerService)
|
||||
{
|
||||
_logger = logger;
|
||||
_config = config;
|
||||
_listServersService = listServersService;
|
||||
_stateService = stateService;
|
||||
_privateServerService = privateServerService;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
@ -42,6 +44,9 @@ namespace WebInterface.Controllers
|
||||
{
|
||||
try
|
||||
{
|
||||
if (parameters.ApiKey != _config.GetValue<string>("ApiKey"))
|
||||
return new StartServerResponse("Invalid ApiKey.");
|
||||
|
||||
Stopwatch sw = Stopwatch.StartNew();
|
||||
while (_isBusy)
|
||||
{
|
||||
@ -52,26 +57,16 @@ namespace WebInterface.Controllers
|
||||
|
||||
_isBusy = true;
|
||||
|
||||
if (parameters.Players < 3)
|
||||
parameters.Players = 3;
|
||||
|
||||
if (parameters.ApiKey != _config.GetValue<string>("ApiKey"))
|
||||
return new StartServerResponse("Invalid ApiKey.");
|
||||
|
||||
SpawnedServerInfo serverProcess = ProcessSpawner.SpawnServer(parameters.Players, parameters.ModName);
|
||||
byte[] payload = Encoding.ASCII.GetBytes($"B{serverProcess.Port}\t{serverProcess.Process.Id}\0");
|
||||
socket.SendTo(payload, webApiListenerEndPoint);
|
||||
|
||||
var serverProcess = _privateServerService.SpawnNewPrivateServer(parameters.Players, parameters.ModName);
|
||||
_logger.LogInformation("Server started waiting for {0} players on port {1}.",
|
||||
parameters.Players, serverProcess.Port);
|
||||
|
||||
string commandLine = CommandLineUtils.GetClientLaunchCommand(HttpContext.Request.Host.Host,
|
||||
serverProcess.Port,
|
||||
serverProcess.Mod.CommandLine);
|
||||
|
||||
Thread.Sleep(TimeSpan.FromSeconds(2));
|
||||
return new StartServerResponse(serverProcess.Port)
|
||||
{
|
||||
CommandLine = CommandLineUtils.GetClientLaunchCommand(HttpContext.Request.Host.Host,
|
||||
serverProcess.Port,
|
||||
serverProcess.Mod.CommandLine)
|
||||
};
|
||||
return new StartServerResponse(serverProcess.Port, commandLine);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -93,7 +88,7 @@ namespace WebInterface.Controllers
|
||||
if (DateTime.UtcNow - _lastRefresh > TimeSpan.FromSeconds(1)
|
||||
|| _lastServerList == null)
|
||||
{
|
||||
_lastServerList = _listServersService.ListServers(HttpContext.Request.Host.Host);
|
||||
_lastServerList = _stateService.ListServers(HttpContext.Request.Host.Host);
|
||||
_lastRefresh = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
|
64
WebInterface/Controllers/PrivateController.cs
Normal file
64
WebInterface/Controllers/PrivateController.cs
Normal file
@ -0,0 +1,64 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Common;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using WebInterface.Services;
|
||||
|
||||
namespace WebInterface.Controllers
|
||||
{
|
||||
public class PrivateController : Controller
|
||||
{
|
||||
private readonly IPrivateServerService _privateServerService;
|
||||
private readonly IRateLimiterService _rateLimiterService;
|
||||
|
||||
public PrivateController(IPrivateServerService privateServerService, IRateLimiterService rateLimiterService)
|
||||
{
|
||||
_privateServerService = privateServerService;
|
||||
_rateLimiterService = rateLimiterService;
|
||||
}
|
||||
|
||||
[Route("nblood/private")]
|
||||
[HttpGet]
|
||||
public IActionResult Index()
|
||||
{
|
||||
var viewModel = new PrivateViewModel
|
||||
{
|
||||
ModName = Constants.SupportedMods.Values.First().Name,
|
||||
Players = 2
|
||||
};
|
||||
|
||||
return View(viewModel);
|
||||
}
|
||||
|
||||
[Route("nblood/private", Name = "RequestPrivateServer")]
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public IActionResult Index(PrivateViewModel request)
|
||||
{
|
||||
StartServerResponse viewModel;
|
||||
try
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
throw new Exception("Something went off the rails.");
|
||||
|
||||
if (!_rateLimiterService.IsRequestAllowed(HttpContext.Connection.RemoteIpAddress))
|
||||
throw new Exception("Sorry, you have requested too many servers recently, you need to wait some time.");
|
||||
|
||||
var spawnedServer = _privateServerService.SpawnNewPrivateServer(request.Players + 1, request.ModName);
|
||||
string commandLine = CommandLineUtils.GetClientLaunchCommand(HttpContext.Request.Host.Host,
|
||||
spawnedServer.Port,
|
||||
spawnedServer.Mod.CommandLine);
|
||||
|
||||
viewModel = new StartServerResponse(spawnedServer.Port, commandLine);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
viewModel = new StartServerResponse(ex.Message);
|
||||
}
|
||||
|
||||
return View("Result", viewModel);
|
||||
}
|
||||
}
|
||||
}
|
22
WebInterface/Models/PrivateViewModel.cs
Normal file
22
WebInterface/Models/PrivateViewModel.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using Common;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
|
||||
namespace WebInterface
|
||||
{
|
||||
public class PrivateViewModel
|
||||
{
|
||||
[Required]
|
||||
[Display(Name = "Required players")]
|
||||
public int Players { get; set; }
|
||||
[Required]
|
||||
[Display(Name = "Select mod")]
|
||||
public string ModName { get; set; }
|
||||
public List<SelectListItem> ModNames { get; } = Constants.SupportedMods
|
||||
.Select(m => new SelectListItem(m.Value.FriendlyName, m.Value.Name))
|
||||
.ToList();
|
||||
}
|
||||
}
|
@ -18,10 +18,11 @@ namespace WebInterface
|
||||
ErrorMessage = errorMessage ?? "";
|
||||
}
|
||||
|
||||
public StartServerResponse(int port)
|
||||
public StartServerResponse(int port, string commandLine)
|
||||
{
|
||||
IsSuccess = true;
|
||||
Port = port;
|
||||
CommandLine = commandLine;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
13
WebInterface/Services/IPrivateServerService.cs
Normal file
13
WebInterface/Services/IPrivateServerService.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using Model;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace WebInterface.Services
|
||||
{
|
||||
public interface IPrivateServerService
|
||||
{
|
||||
SpawnedServerInfo SpawnNewPrivateServer(int players, string modName);
|
||||
}
|
||||
}
|
13
WebInterface/Services/IRateLimiterService.cs
Normal file
13
WebInterface/Services/IRateLimiterService.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace WebInterface.Services
|
||||
{
|
||||
public interface IRateLimiterService
|
||||
{
|
||||
bool IsRequestAllowed(IPAddress remoteIpAddress);
|
||||
}
|
||||
}
|
29
WebInterface/Services/PrivateServerService.cs
Normal file
29
WebInterface/Services/PrivateServerService.cs
Normal file
@ -0,0 +1,29 @@
|
||||
using Common;
|
||||
using Model;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace WebInterface.Services
|
||||
{
|
||||
public class PrivateServerService : IPrivateServerService
|
||||
{
|
||||
private static readonly Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
|
||||
private static readonly IPEndPoint webApiListenerEndPoint = new IPEndPoint(IPAddress.Loopback, 11028);
|
||||
|
||||
public SpawnedServerInfo SpawnNewPrivateServer(int players, string modName)
|
||||
{
|
||||
players = Math.Min(8, Math.Max(3, players));
|
||||
|
||||
SpawnedServerInfo serverProcess = ProcessSpawner.SpawnServer(players, modName);
|
||||
byte[] payload = Encoding.ASCII.GetBytes($"B{serverProcess.Port}\t{serverProcess.Process.Id}\0");
|
||||
socket.SendTo(payload, webApiListenerEndPoint);
|
||||
|
||||
return serverProcess;
|
||||
}
|
||||
}
|
||||
}
|
39
WebInterface/Services/RateLimiterService.cs
Normal file
39
WebInterface/Services/RateLimiterService.cs
Normal file
@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace WebInterface.Services
|
||||
{
|
||||
public class RateLimiterService : IRateLimiterService
|
||||
{
|
||||
private static readonly ConcurrentDictionary<IPAddress, List<DateTime>> requestHistory = new ConcurrentDictionary<IPAddress, List<DateTime>>();
|
||||
|
||||
public bool IsRequestAllowed(IPAddress remoteIpAddress)
|
||||
{
|
||||
if (requestHistory.TryGetValue(remoteIpAddress, out var list))
|
||||
{
|
||||
bool isLimited = list.Count(e => DateTime.UtcNow - e < TimeSpan.FromHours(1)) >= 5;
|
||||
if (isLimited)
|
||||
return false;
|
||||
}
|
||||
|
||||
requestHistory.AddOrUpdate(remoteIpAddress,
|
||||
(ip) =>
|
||||
{
|
||||
var list = new List<DateTime>();
|
||||
list.Add(DateTime.UtcNow);
|
||||
return list;
|
||||
},
|
||||
(ip, list) =>
|
||||
{
|
||||
list.Add(DateTime.UtcNow);
|
||||
return list;
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@ -40,6 +40,8 @@ namespace WebInterface
|
||||
services.AddControllers();
|
||||
services.AddControllersWithViews();
|
||||
services.Add(new ServiceDescriptor(typeof(IStateService), typeof(StateService), ServiceLifetime.Singleton));
|
||||
services.Add(new ServiceDescriptor(typeof(IPrivateServerService), typeof(PrivateServerService), ServiceLifetime.Transient));
|
||||
services.Add(new ServiceDescriptor(typeof(IRateLimiterService), typeof(RateLimiterService), ServiceLifetime.Singleton));
|
||||
}
|
||||
|
||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
|
@ -1,7 +1,9 @@
|
||||
@model WebInterface.HomeViewModel
|
||||
@model HomeViewModel
|
||||
|
||||
@{
|
||||
Layout = null;
|
||||
ViewData["Title"] = "Blood Servers";
|
||||
Layout = "~/Views/Shared/_Layout.cshtml";
|
||||
|
||||
string b = "Blood";
|
||||
string cp = "Cryptic Passage";
|
||||
string dw = "Death Wish";
|
||||
@ -12,7 +14,7 @@
|
||||
ListServers = @<div>
|
||||
@foreach (var server in Model.Servers.Where(s => s.Mod == item).OrderBy(s => s.MaximumPlayers))
|
||||
{
|
||||
<div style="border:2px solid cornflowerblue;padding-left:1em">
|
||||
<div class="bordered">
|
||||
<p style="font-weight:bold">Players: @(server.CurrentPlayers - 1)/@(server.MaximumPlayers - 1)</p>
|
||||
<div>
|
||||
@if (server.IsStarted)
|
||||
@ -38,128 +40,98 @@
|
||||
</div>;
|
||||
}
|
||||
|
||||
<!DOCTYPE html>
|
||||
<h1>The client EXE</h1>
|
||||
<p>Use this exe to connect: <a href="https://lerppu.net/wannabethesis/nblood/20200113-2073/">https://lerppu.net/wannabethesis/nblood/20200113-2073/</a></p>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<title>Blood servers</title>
|
||||
<style>
|
||||
.code {
|
||||
font-family: Consolas;
|
||||
background-color: black;
|
||||
color: whitesmoke;
|
||||
}
|
||||
<p><span class="bolder" style="font-size:larger">@Model.ManHoursPlayed</span> man-hours played since @Model.RunningSinceUtc</p>
|
||||
|
||||
.warning {
|
||||
border: dashed 4px red;
|
||||
font-weight: bold;
|
||||
}
|
||||
<div class="bordered" style="padding-left:1em;padding-bottom:1em">
|
||||
<p>If you want to play with friends only and keep your port secret, request a private server by clicking the button below:</p>
|
||||
<form method="get">
|
||||
<button asp-route="RequestPrivateServer">Request Private Server</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
.bolder {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
.main {
|
||||
margin: auto;
|
||||
max-width: 900px;
|
||||
padding: 10px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body style="background-color: cornsilk">
|
||||
<div class="main">
|
||||
<h1>The client EXE</h1>
|
||||
<p>Use this exe to connect: <a href="https://lerppu.net/wannabethesis/nblood/20200113-2073/">https://lerppu.net/wannabethesis/nblood/20200113-2073/</a></p>
|
||||
|
||||
<p><span class="bolder" style="font-size:larger">@Model.ManHoursPlayed</span> man-hours played since @Model.RunningSinceUtc</p>
|
||||
|
||||
<h2>@b 1.21</h2>
|
||||
<details>
|
||||
<summary>Show @b <span class="bolder">version 1.21</span> servers</summary>
|
||||
<p>The below files must be in your Blood directory:</p>
|
||||
<h2>@b 1.21</h2>
|
||||
<details>
|
||||
<summary>Show @b <span class="bolder">version 1.21</span> servers</summary>
|
||||
<p>The below files must be in your Blood directory:</p>
|
||||
<ul>
|
||||
<li>BLOOD.INI</li>
|
||||
<li>BLOOD.RFF</li>
|
||||
<li>GUI.RFF</li>
|
||||
<li>SOUNDS.RFF</li>
|
||||
<li>SURFACE.DAT</li>
|
||||
<li>TILES000.ART-TILES017.ART</li>
|
||||
<li>VOXEL.DAT</li>
|
||||
</ul>
|
||||
@ListServers(@b)
|
||||
</details>
|
||||
<br />
|
||||
<h2>@cp</h2>
|
||||
<details>
|
||||
<summary>Show @cp servers</summary>
|
||||
<p>The below files must be in your Blood directory:</p>
|
||||
<ul>
|
||||
<li>CP01-CP09.MAP</li>
|
||||
<li>CPART07.AR_ (Fresh Supply owners need to copy tiles007.ART from <span class="code">\addons\Cryptic Passage</span> and rename it</li>
|
||||
<li>CPART15.AR_ (Fresh Supply owners need to copy tiles015.ART from <span class="code">\addons\Cryptic Passage</span> and rename it</li>
|
||||
<li>CPBB01.MAP-CPBB04.MAP</li>
|
||||
<li>CPSL.MAP</li>
|
||||
<li>CRYPTIC.INI</li>
|
||||
</ul>
|
||||
<p class="warning">Don't forget to send back the ferry every time at the end of the last map, otherwise you cannot go back if the boss kills you!</p>
|
||||
@ListServers(@cp)
|
||||
</details>
|
||||
<br />
|
||||
<h2>@dw 1.6.10</h2>
|
||||
<details>
|
||||
<summary>Show @dw <span class="bolder">version 1.6.10</span> servers</summary>
|
||||
<p>The below files must be in your Blood directory:</p>
|
||||
<ul>
|
||||
<li>dw.ini</li>
|
||||
<li>DWBB1.MAP-DWBB3.MAP</li>
|
||||
<li>DWE1M1.MAP-DWE1M12.MAP</li>
|
||||
<li>DWE2M1.MAP-DWE2M12.MAP</li>
|
||||
<li>DWE3M1.MAP-DWE3M12.MAP</li>
|
||||
</ul>
|
||||
@ListServers(@dw)
|
||||
</details>
|
||||
<br />
|
||||
<h2>@twoira 1.0.1</h2>
|
||||
<details>
|
||||
<summary>Show @twoira <span class="bolder">version 1.0.1</span> servers</summary>
|
||||
<p>@twoira </p>
|
||||
<p>The below folder (TWOIRA) must be in your Blood directory, and inside that the other additional files:</p>
|
||||
<ul>
|
||||
<li>TWOIRA</li>
|
||||
<li>
|
||||
<ul>
|
||||
<li>BLOOD.INI</li>
|
||||
<li>BLOOD.RFF</li>
|
||||
<li>GUI.RFF</li>
|
||||
<li>SOUNDS.RFF</li>
|
||||
<li>IRA01.MAP</li>
|
||||
<li>IRA02_A.MAP</li>
|
||||
<li>IRA02_B.MAP</li>
|
||||
<li>IRA03.MAP</li>
|
||||
<li>IRA04.MAP</li>
|
||||
<li>IRA05.MAP</li>
|
||||
<li>IRA06.MAP</li>
|
||||
<li>IRA07.MAP</li>
|
||||
<li>IRA08.MAP</li>
|
||||
<li>SURFACE.DAT</li>
|
||||
<li>TILES000.ART-TILES017.ART</li>
|
||||
<li>VOXEL.DAT</li>
|
||||
<li>TILES18.ART</li>
|
||||
<li>twoira.ini</li>
|
||||
</ul>
|
||||
@ListServers(@b)
|
||||
</details>
|
||||
<br />
|
||||
<h2>@cp</h2>
|
||||
<details>
|
||||
<summary>Show @cp servers</summary>
|
||||
<p>The below files must be in your Blood directory:</p>
|
||||
<ul>
|
||||
<li>CP01-CP09.MAP</li>
|
||||
<li>CPART07.AR_ (Fresh Supply owners need to copy tiles007.ART from <span class="code">\addons\Cryptic Passage</span> and rename it</li>
|
||||
<li>CPART15.AR_ (Fresh Supply owners need to copy tiles015.ART from <span class="code">\addons\Cryptic Passage</span> and rename it</li>
|
||||
<li>CPBB01.MAP-CPBB04.MAP</li>
|
||||
<li>CPSL.MAP</li>
|
||||
<li>CRYPTIC.INI</li>
|
||||
</ul>
|
||||
<p class="warning">Don't forget to send back the ferry every time at the end of the last map, otherwise you cannot go back if the boss kills you!</p>
|
||||
@ListServers(@cp)
|
||||
</details>
|
||||
<br />
|
||||
<h2>@dw 1.6.10</h2>
|
||||
<details>
|
||||
<summary>Show @dw <span class="bolder">version 1.6.10</span> servers</summary>
|
||||
<p>The below files must be in your Blood directory:</p>
|
||||
<ul>
|
||||
<li>dw.ini</li>
|
||||
<li>DWBB1.MAP-DWBB3.MAP</li>
|
||||
<li>DWE1M1.MAP-DWE1M12.MAP</li>
|
||||
<li>DWE2M1.MAP-DWE2M12.MAP</li>
|
||||
<li>DWE3M1.MAP-DWE3M12.MAP</li>
|
||||
</ul>
|
||||
@ListServers(@dw)
|
||||
</details>
|
||||
<br />
|
||||
<h2>@twoira 1.0.1</h2>
|
||||
<details>
|
||||
<summary>Show @twoira <span class="bolder">version 1.0.1</span> servers</summary>
|
||||
<p>@twoira </p>
|
||||
<p>The below folder (TWOIRA) must be in your Blood directory, and inside that the other additional files:</p>
|
||||
<ul>
|
||||
<li>TWOIRA</li>
|
||||
<li>
|
||||
<ul>
|
||||
<li>IRA01.MAP</li>
|
||||
<li>IRA02_A.MAP</li>
|
||||
<li>IRA02_B.MAP</li>
|
||||
<li>IRA03.MAP</li>
|
||||
<li>IRA04.MAP</li>
|
||||
<li>IRA05.MAP</li>
|
||||
<li>IRA06.MAP</li>
|
||||
<li>IRA07.MAP</li>
|
||||
<li>IRA08.MAP</li>
|
||||
<li>SURFACE.DAT</li>
|
||||
<li>TILES18.ART</li>
|
||||
<li>twoira.ini</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
@ListServers(twoira)
|
||||
</details>
|
||||
<h2>@fo 1.3</h2>
|
||||
<details>
|
||||
<summary>Show @fo <span class="bolder">version 1.3</span> servers</summary>
|
||||
<p>The below files must be in your Blood directory:</p>
|
||||
<ul>
|
||||
<li>fo.INI</li>
|
||||
<li>fo1m1.MAP-fo1m8.MAP</li>
|
||||
</ul>
|
||||
<p class="warning">Not tested in co-op! But I hope it works! :)</p>
|
||||
@ListServers(@fo)
|
||||
</details>
|
||||
<br />
|
||||
<hr />
|
||||
<p>Do you think this page is ugly? You are right! If you want to make it look better, PRs are welcomed (but please, don't use any JavaScript, thanks): <a href="https://github.com/CommonLoon102/NBloodServerSupervisor">https://github.com/CommonLoon102/NBloodServerSupervisor</a></p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</li>
|
||||
</ul>
|
||||
@ListServers(twoira)
|
||||
</details>
|
||||
<h2>@fo 1.3</h2>
|
||||
<details>
|
||||
<summary>Show @fo <span class="bolder">version 1.3</span> servers</summary>
|
||||
<p>The below files must be in your Blood directory:</p>
|
||||
<ul>
|
||||
<li>fo.INI</li>
|
||||
<li>fo1m1.MAP-fo1m8.MAP</li>
|
||||
</ul>
|
||||
<p class="warning">Not tested in co-op! But I hope it works! :)</p>
|
||||
@ListServers(@fo)
|
||||
</details>
|
||||
|
16
WebInterface/Views/Private/Index.cshtml
Normal file
16
WebInterface/Views/Private/Index.cshtml
Normal file
@ -0,0 +1,16 @@
|
||||
@model PrivateViewModel
|
||||
@{
|
||||
ViewData["Title"] = "Request Private Server";
|
||||
Layout = "~/Views/Shared/_Layout.cshtml";
|
||||
}
|
||||
|
||||
<h1>@ViewData["Title"]</h1>
|
||||
<div class="bordered" style="padding:4px">
|
||||
<form method="post">
|
||||
<label asp-for="ModName"></label>
|
||||
<select asp-for="ModName" asp-items="Model.ModNames"></select> <br />
|
||||
<label asp-for="Players"></label>
|
||||
<input type="number" min="2" max="7" asp-for="Players" /> <br />
|
||||
<button asp-route="RequestPrivateServer">Request</button>
|
||||
</form>
|
||||
</div>
|
17
WebInterface/Views/Private/Result.cshtml
Normal file
17
WebInterface/Views/Private/Result.cshtml
Normal file
@ -0,0 +1,17 @@
|
||||
@model StartServerResponse
|
||||
@{
|
||||
ViewData["Title"] = "Your Private Server";
|
||||
Layout = "~/Views/Shared/_Layout.cshtml";
|
||||
}
|
||||
|
||||
<div class="bordered">
|
||||
@if (Model.IsSuccess)
|
||||
{
|
||||
<p>Command: <span class="code">@Model.CommandLine</span></p>
|
||||
<p>You have 10 minutes to join.</p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<p>Error: @Model.ErrorMessage</p>
|
||||
}
|
||||
</div>
|
59
WebInterface/Views/Shared/_Layout.cshtml
Normal file
59
WebInterface/Views/Shared/_Layout.cshtml
Normal file
@ -0,0 +1,59 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>@ViewData["Title"]</title>
|
||||
<style>
|
||||
.code {
|
||||
font-family: Consolas;
|
||||
background-color: black;
|
||||
color: whitesmoke;
|
||||
}
|
||||
|
||||
.warning {
|
||||
border: dashed 4px red;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.bolder {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
.main {
|
||||
margin: auto;
|
||||
max-width: 900px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.bordered {
|
||||
border: 2px solid cornflowerblue;
|
||||
padding-left: 1em;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body style="background-color: cornsilk">
|
||||
<div class="main">
|
||||
<header>
|
||||
<form method="get">
|
||||
<button asp-route="Home">Home</button>
|
||||
</form>
|
||||
<hr />
|
||||
<p>I live... Again!</p>
|
||||
</header>
|
||||
|
||||
<div>
|
||||
<main role="main">
|
||||
@RenderBody()
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<footer class="border-top footer text-muted">
|
||||
<div>
|
||||
<hr />
|
||||
<p>Do you think this page is ugly? You are right! If you want to make it look better, PRs are welcomed (but please, don't use any JavaScript, thanks): <a href="https://github.com/CommonLoon102/NBloodServerSupervisor">https://github.com/CommonLoon102/NBloodServerSupervisor</a></p>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
2
WebInterface/Views/_ViewImports.cshtml
Normal file
2
WebInterface/Views/_ViewImports.cshtml
Normal file
@ -0,0 +1,2 @@
|
||||
@using WebInterface
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
Loading…
Reference in New Issue
Block a user