Made Front-end use Razor instead of static html
This commit is contained in:
32
.dockerignore
Normal file
32
.dockerignore
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# Include any files or directories that you don't want to be copied to your
|
||||||
|
# container here (e.g., local build artifacts, temporary files, etc.).
|
||||||
|
#
|
||||||
|
# For more help, visit the .dockerignore file reference guide at
|
||||||
|
# https://docs.docker.com/go/build-context-dockerignore/
|
||||||
|
|
||||||
|
**/.DS_Store
|
||||||
|
**/.classpath
|
||||||
|
**/.dockerignore
|
||||||
|
**/.env
|
||||||
|
**/.git
|
||||||
|
**/.gitignore
|
||||||
|
**/.project
|
||||||
|
**/.settings
|
||||||
|
**/.toolstarget
|
||||||
|
**/.vs
|
||||||
|
**/.vscode
|
||||||
|
**/*.*proj.user
|
||||||
|
**/*.dbmdl
|
||||||
|
**/*.jfm
|
||||||
|
**/bin
|
||||||
|
**/charts
|
||||||
|
**/docker-compose*
|
||||||
|
**/compose.y*ml
|
||||||
|
**/Dockerfile*
|
||||||
|
**/node_modules
|
||||||
|
**/npm-debug.log
|
||||||
|
**/obj
|
||||||
|
**/secrets.dev.yaml
|
||||||
|
**/values.dev.yaml
|
||||||
|
LICENSE
|
||||||
|
README.md
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -362,4 +362,4 @@ MigrationBackup/
|
|||||||
# Fody - auto-generated XML schema
|
# Fody - auto-generated XML schema
|
||||||
FodyWeavers.xsd
|
FodyWeavers.xsd
|
||||||
|
|
||||||
appsettings.production.json
|
Certs
|
||||||
56
Dockerfile
Normal file
56
Dockerfile
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
|
# Comments are provided throughout this file to help you get started.
|
||||||
|
# If you need more help, visit the Dockerfile reference guide at
|
||||||
|
# https://docs.docker.com/go/dockerfile-reference/
|
||||||
|
|
||||||
|
# Want to help us make this template better? Share your feedback here: https://forms.gle/ybq9Krt8jtBL3iCk7
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
# Learn about building .NET container images:
|
||||||
|
# https://github.com/dotnet/dotnet-docker/blob/main/samples/README.md
|
||||||
|
|
||||||
|
# Create a stage for building the application.
|
||||||
|
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0-alpine AS build
|
||||||
|
|
||||||
|
COPY . /source
|
||||||
|
|
||||||
|
WORKDIR /source/PurrLobby
|
||||||
|
|
||||||
|
# This is the architecture you’re building for, which is passed in by the builder.
|
||||||
|
# Placing it here allows the previous steps to be cached across architectures.
|
||||||
|
ARG TARGETARCH
|
||||||
|
|
||||||
|
# Build the application.
|
||||||
|
# Leverage a cache mount to /root/.nuget/packages so that subsequent builds don't have to re-download packages.
|
||||||
|
# If TARGETARCH is "amd64", replace it with "x64" - "x64" is .NET's canonical name for this and "amd64" doesn't
|
||||||
|
# work in .NET 6.0.
|
||||||
|
RUN --mount=type=cache,id=nuget,target=/root/.nuget/packages \
|
||||||
|
dotnet publish -a ${TARGETARCH/amd64/x64} --use-current-runtime --self-contained false -o /app
|
||||||
|
|
||||||
|
# If you need to enable globalization and time zones:
|
||||||
|
# https://github.com/dotnet/dotnet-docker/blob/main/samples/enable-globalization.md
|
||||||
|
################################################################################
|
||||||
|
# Create a new stage for running the application that contains the minimal
|
||||||
|
# runtime dependencies for the application. This often uses a different base
|
||||||
|
# image from the build stage where the necessary files are copied from the build
|
||||||
|
# stage.
|
||||||
|
#
|
||||||
|
# The example below uses an aspnet alpine image as the foundation for running the app.
|
||||||
|
# It will also use whatever happens to be the most recent version of that tag when you
|
||||||
|
# build your Dockerfile. If reproducibility is important, consider using a more specific
|
||||||
|
# version (e.g., aspnet:7.0.10-alpine-3.18),
|
||||||
|
# or SHA (e.g., mcr.microsoft.com/dotnet/aspnet@sha256:f3d99f54d504a21d38e4cc2f13ff47d67235efeeb85c109d3d1ff1808b38d034).
|
||||||
|
FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine AS final
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy everything needed to run the app from the "build" stage.
|
||||||
|
COPY --from=build /app .
|
||||||
|
|
||||||
|
# Switch to a non-privileged user (defined in the base image) that the app will run under.
|
||||||
|
# See https://docs.docker.com/go/dockerfile-user-best-practices/
|
||||||
|
# and https://github.com/dotnet/dotnet-docker/discussions/4764
|
||||||
|
USER $APP_UID
|
||||||
|
|
||||||
|
ENTRYPOINT ["dotnet", "PurrLobby.dll"]
|
||||||
@@ -5,6 +5,7 @@ public class LobbyUser
|
|||||||
{
|
{
|
||||||
public required string Id { get; init; }
|
public required string Id { get; init; }
|
||||||
public required string DisplayName { get; init; }
|
public required string DisplayName { get; init; }
|
||||||
|
public int userPing { get; set; }
|
||||||
public bool IsReady { get; set; }
|
public bool IsReady { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
90
PurrLobby/Pages/Index.cshtml
Normal file
90
PurrLobby/Pages/Index.cshtml
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
@page
|
||||||
|
@model PurrLobby.Pages.IndexModel
|
||||||
|
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<title>PurrLobby</title>
|
||||||
|
<link rel="icon" href="/favicon.ico" type="image/x-icon" />
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--bg: #000;
|
||||||
|
--text: #fff;
|
||||||
|
--muted: #aaa;
|
||||||
|
--border: #333;
|
||||||
|
--accent: #2aa9ff;
|
||||||
|
}
|
||||||
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
body { background: var(--bg); color: var(--text); font-family: monospace, sans-serif; line-height: 1.5; }
|
||||||
|
.container { max-width: 800px; margin: 20px auto; padding: 0 12px; }
|
||||||
|
.hero { display: flex; align-items: center; gap: 12px; margin-bottom: 20px; }
|
||||||
|
.logo { width: 64px; height: 64px; object-fit: contain; }
|
||||||
|
.title { font-size: 32px; }
|
||||||
|
.section-title { font-size: 14px; margin: 20px 0 8px; text-transform: uppercase; }
|
||||||
|
.center { text-align: center; }
|
||||||
|
.bigstats { display: flex; gap: 20px; justify-content: center; flex-wrap: wrap; margin-bottom: 20px; }
|
||||||
|
.bigstat { min-width: 120px; display: flex; flex-direction: column; align-items: center; }
|
||||||
|
.bignum { font-size: 32px; font-weight: bold; text-align: center; }
|
||||||
|
.biglabel { font-size: 12px; color: var(--muted); text-align: center; }
|
||||||
|
.stack { display: grid; gap: 8px; margin-bottom: 20px; }
|
||||||
|
#gameIdInput { padding: 8px; border: 1px solid var(--border); background: var(--bg); color: var(--text); }
|
||||||
|
button { padding: 8px; border: 1px solid var(--border); background: #111; color: var(--text); cursor: pointer; }
|
||||||
|
button:hover { background: #222; }
|
||||||
|
.disclaimer { font-size: 13px; color: var(--muted); margin-top: 20px; }
|
||||||
|
a { color: var(--accent); }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<header class="hero">
|
||||||
|
<img class="logo" src="/purrlogo.png" alt="PurrLobby" />
|
||||||
|
<h1 class="title">PurrLobby</h1>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<h2 class="section-title center">Global Stats</h2>
|
||||||
|
<div class="bigstats">
|
||||||
|
<div class="bigstat">
|
||||||
|
<div class="bignum">@Model.GlobalPlayers</div>
|
||||||
|
<div class="biglabel">Players</div>
|
||||||
|
</div>
|
||||||
|
<div class="bigstat">
|
||||||
|
<div class="bignum">@Model.GlobalLobbies</div>
|
||||||
|
<div class="biglabel">Lobbies</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 class="section-title">Track Your Game</h2>
|
||||||
|
<form method="post" class="stack">
|
||||||
|
<input id="gameIdInput" asp-for="GameIdInput" type="text" placeholder="Enter Game ID (GUID)" required />
|
||||||
|
<button type="submit">Set Game</button>
|
||||||
|
</form>
|
||||||
|
<small id="cookieStatus" class="muted">@Model.CookieStatus</small>
|
||||||
|
|
||||||
|
<h2 class="section-title center">Game Stats</h2>
|
||||||
|
<div class="bigstats">
|
||||||
|
<div class="bigstat">
|
||||||
|
<div class="bignum">@Model.GamePlayers</div>
|
||||||
|
<div class="biglabel">Players</div>
|
||||||
|
</div>
|
||||||
|
<div class="bigstat">
|
||||||
|
<div class="bignum">@Model.GameLobbies</div>
|
||||||
|
<div class="biglabel">Lobbies</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="center" style="margin: 20px 0;">
|
||||||
|
<a href="/swagger" class="btn" target="_blank">Swagger API Docs</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="disclaimer">
|
||||||
|
This Service does not (yet) belong to purrnet, it's just something I put together for an alternative to the Steam Lobby Service and Unity Service.
|
||||||
|
Since it's running on my own setup, it might go down or act weird sometimes.
|
||||||
|
If that happens, no worries, it'll probably be back soon.
|
||||||
|
Please don't contact the actual PurrNet devs about this, it's all on me.
|
||||||
|
If there are any problems contact me on discord: exil_s
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
91
PurrLobby/Pages/Index.cshtml.cs
Normal file
91
PurrLobby/Pages/Index.cshtml.cs
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace PurrLobby.Pages
|
||||||
|
{
|
||||||
|
public class IndexModel : PageModel
|
||||||
|
{
|
||||||
|
[BindProperty]
|
||||||
|
public string GameIdInput { get; set; } = string.Empty;
|
||||||
|
public string CookieStatus { get; set; } = string.Empty;
|
||||||
|
public int GlobalPlayers { get; set; } = 0;
|
||||||
|
public int GlobalLobbies { get; set; } = 0;
|
||||||
|
public int GamePlayers { get; set; } = 0;
|
||||||
|
public int GameLobbies { get; set; } = 0;
|
||||||
|
|
||||||
|
public async Task OnGetAsync()
|
||||||
|
{
|
||||||
|
await LoadStatsAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IActionResult> OnPostAsync()
|
||||||
|
{
|
||||||
|
if (!Guid.TryParse(GameIdInput, out var gameId))
|
||||||
|
{
|
||||||
|
CookieStatus = "Invalid Game ID format.";
|
||||||
|
await LoadStatsAsync();
|
||||||
|
return Page();
|
||||||
|
}
|
||||||
|
// Set gameId cookie via backend API
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var client = new HttpClient();
|
||||||
|
var response = await client.PostAsync($"{Request.Scheme}://{Request.Host}/session/game", new StringContent(JsonSerializer.Serialize(new { GameId = gameId }), System.Text.Encoding.UTF8, "application/json"));
|
||||||
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
CookieStatus = "Game ID set!";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CookieStatus = "Failed to set Game ID.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
CookieStatus = "Error contacting backend.";
|
||||||
|
}
|
||||||
|
await LoadStatsAsync(gameId);
|
||||||
|
return Page();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadStatsAsync(Guid? gameId = null)
|
||||||
|
{
|
||||||
|
using var client = new HttpClient();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var globalPlayersResp = await client.GetStringAsync($"{Request.Scheme}://{Request.Host}/stats/global/players");
|
||||||
|
GlobalPlayers = int.TryParse(globalPlayersResp, out var gp) ? gp : 0;
|
||||||
|
}
|
||||||
|
catch { GlobalPlayers = 0; }
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var globalLobbiesResp = await client.GetStringAsync($"{Request.Scheme}://{Request.Host}/stats/global/lobbies");
|
||||||
|
GlobalLobbies = int.TryParse(globalLobbiesResp, out var gl) ? gl : 0;
|
||||||
|
}
|
||||||
|
catch { GlobalLobbies = 0; }
|
||||||
|
if (gameId.HasValue)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var gamePlayersResp = await client.GetStringAsync($"{Request.Scheme}://{Request.Host}/stats/{gameId}/players");
|
||||||
|
GamePlayers = int.TryParse(gamePlayersResp, out var gp) ? gp : 0;
|
||||||
|
}
|
||||||
|
catch { GamePlayers = 0; }
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var gameLobbiesResp = await client.GetStringAsync($"{Request.Scheme}://{Request.Host}/stats/{gameId}/lobbies");
|
||||||
|
GameLobbies = int.TryParse(gameLobbiesResp, out var gl) ? gl : 0;
|
||||||
|
}
|
||||||
|
catch { GameLobbies = 0; }
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GamePlayers = 0;
|
||||||
|
GameLobbies = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -86,6 +86,7 @@ builder.Services.AddSwaggerGen(o =>
|
|||||||
});
|
});
|
||||||
builder.Services.AddSingleton<ILobbyEventHub, LobbyEventHub>();
|
builder.Services.AddSingleton<ILobbyEventHub, LobbyEventHub>();
|
||||||
builder.Services.AddSingleton<ILobbyService, LobbyService>();
|
builder.Services.AddSingleton<ILobbyService, LobbyService>();
|
||||||
|
builder.Services.AddRazorPages(); // Register Razor Pages services
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
if (!app.Environment.IsDevelopment())
|
if (!app.Environment.IsDevelopment())
|
||||||
{
|
{
|
||||||
@@ -102,10 +103,10 @@ app.UseResponseCompression();
|
|||||||
app.UseHttpLogging();
|
app.UseHttpLogging();
|
||||||
app.UseRateLimiter();
|
app.UseRateLimiter();
|
||||||
app.UseWebSockets();
|
app.UseWebSockets();
|
||||||
app.UseDefaultFiles();
|
|
||||||
app.UseStaticFiles();
|
app.UseStaticFiles();
|
||||||
app.UseSwagger();
|
app.UseSwagger();
|
||||||
app.UseSwaggerUI();
|
app.UseSwaggerUI();
|
||||||
|
app.MapRazorPages(); // Enable Razor Pages
|
||||||
static CookieOptions BuildStdCookieOptions() => new()
|
static CookieOptions BuildStdCookieOptions() => new()
|
||||||
{
|
{
|
||||||
HttpOnly = true,
|
HttpOnly = true,
|
||||||
|
|||||||
@@ -26,4 +26,14 @@
|
|||||||
</Content>
|
</Content>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<!-- Copy certificates to output directory -->
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include="Certs\*.pem">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
<Content Include="Certs\*.key">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"Kestrel": {
|
|
||||||
"Endpoints": {
|
|
||||||
"Https": {
|
|
||||||
"Url": "https://0.0.0.0:443",
|
|
||||||
"Certificate": {
|
|
||||||
"Path": "pemfile",
|
|
||||||
"KeyPath": "keyfile"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"AllowedHosts": "your.domain.com"
|
|
||||||
}
|
|
||||||
20
PurrLobby/appsettings.json
Normal file
20
PurrLobby/appsettings.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"AllowedHosts": "purrlobby.exil.dev",
|
||||||
|
"Kestrel": {
|
||||||
|
"Endpoints": {
|
||||||
|
"Https": {
|
||||||
|
"Url": "https://0.0.0.0:443",
|
||||||
|
"Certificate": {
|
||||||
|
"Path": "Certs/purrlobby.pem",
|
||||||
|
"KeyPath": "Certs/purrlobby.key"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
<title>PurrLobby</title>
|
|
||||||
<link rel="icon" href="/favicon.ico" type="image/x-icon" />
|
|
||||||
<link rel="stylesheet" href="/styles.css?v=5" />
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<header class="hero">
|
|
||||||
<img class="logo" src="/purrlogo.png" alt="PurrLobby" />
|
|
||||||
<h1 class="title">PurrLobby</h1>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<h2 class="section-title center">Global Stats</h2>
|
|
||||||
<div class="bigstats">
|
|
||||||
<div class="bigstat">
|
|
||||||
<div class="bignum" id="globalPlayers">-</div>
|
|
||||||
<div class="biglabel">Players</div>
|
|
||||||
</div>
|
|
||||||
<div class="bigstat">
|
|
||||||
<div class="bignum" id="globalLobbies">-</div>
|
|
||||||
<div class="biglabel">Lobbies</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h2 class="section-title">Track Your Game</h2>
|
|
||||||
<form id="gameForm" class="stack">
|
|
||||||
<input id="gameIdInput" type="text" placeholder="Enter Game ID (GUID)" required />
|
|
||||||
<button type="submit">Set Game</button>
|
|
||||||
</form>
|
|
||||||
<small id="cookieStatus" class="muted"></small>
|
|
||||||
|
|
||||||
<h2 class="section-title center">Game Stats</h2>
|
|
||||||
<div class="bigstats">
|
|
||||||
<div class="bigstat">
|
|
||||||
<div class="bignum" id="gamePlayers">-</div>
|
|
||||||
<div class="biglabel">Players</div>
|
|
||||||
</div>
|
|
||||||
<div class="bigstat">
|
|
||||||
<div class="bignum" id="gameLobbies">-</div>
|
|
||||||
<div class="biglabel">Lobbies</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="center" style="margin: 20px 0;">
|
|
||||||
<a href="/swagger" class="btn" target="_blank">Swagger API Docs</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p class="disclaimer">
|
|
||||||
This Service is does not (yet) belong to purrnet, it's just something I put together for a alternative to the Steam Lobby Service and Unity Service.
|
|
||||||
Since it's running on my own setup, it might go down or act weird sometimes.
|
|
||||||
If that happens, no worries, it'll probably be back soon.
|
|
||||||
Please don't contact the actual PurrNet devs about this, it's all on me.
|
|
||||||
If there are any proplem's contact me on discord: exil_s
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="/main.js" type="module"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
async function fetchJson(url) {
|
|
||||||
const r = await fetch(url, { credentials: 'include' });
|
|
||||||
if (!r.ok) throw new Error(`Request failed: ${r.status}`);
|
|
||||||
return r.json();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadGlobal() {
|
|
||||||
try {
|
|
||||||
const [players, lobbies] = await Promise.all([
|
|
||||||
fetchJson('/stats/global/players'),
|
|
||||||
fetchJson('/stats/global/lobbies')
|
|
||||||
]);
|
|
||||||
document.getElementById('globalPlayers').textContent = players;
|
|
||||||
document.getElementById('globalLobbies').textContent = lobbies;
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function loadGame(gameId) {
|
|
||||||
try {
|
|
||||||
const [lobbies, players] = await Promise.all([
|
|
||||||
fetchJson(`/stats/${gameId}/lobbies`),
|
|
||||||
fetchJson(`/stats/${gameId}/players`)
|
|
||||||
]);
|
|
||||||
document.getElementById('gameLobbies').textContent = lobbies;
|
|
||||||
document.getElementById('gamePlayers').textContent = players.length ?? players; // endpoint returns list
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function setGameCookie(gameId) {
|
|
||||||
const r = await fetch('/session/game', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ gameId }),
|
|
||||||
credentials: 'include'
|
|
||||||
});
|
|
||||||
if (!r.ok) throw new Error('Failed to set game cookie');
|
|
||||||
return r.json();
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCookie(name) {
|
|
||||||
const m = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
|
|
||||||
return m ? decodeURIComponent(m[2]) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
(async () => {
|
|
||||||
await loadGlobal();
|
|
||||||
|
|
||||||
const gameForm = document.getElementById('gameForm');
|
|
||||||
const gameIdInput = document.getElementById('gameIdInput');
|
|
||||||
const cookieStatus = document.getElementById('cookieStatus');
|
|
||||||
|
|
||||||
//if cookie exists prefill and load stats
|
|
||||||
const existing = getCookie('gameId');
|
|
||||||
if (existing) {
|
|
||||||
gameIdInput.value = existing;
|
|
||||||
loadGame(existing);
|
|
||||||
cookieStatus.textContent = `Using gameId from cookie.`;
|
|
||||||
}
|
|
||||||
|
|
||||||
gameForm.addEventListener('submit', async (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
const gameId = gameIdInput.value.trim();
|
|
||||||
if (!/^\{?[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-5][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}\}?$/.test(gameId)) {
|
|
||||||
alert('Please enter a valid GUID');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
await setGameCookie(gameId);
|
|
||||||
cookieStatus.textContent = 'GameId stored in cookie.';
|
|
||||||
await loadGame(gameId);
|
|
||||||
} catch (err) {
|
|
||||||
cookieStatus.textContent = 'Failed to set cookie';
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})();
|
|
||||||
@@ -1,115 +0,0 @@
|
|||||||
:root {
|
|
||||||
--bg: #000;
|
|
||||||
--text: #fff;
|
|
||||||
--muted: #aaa;
|
|
||||||
--border: #333;
|
|
||||||
--accent: #2aa9ff;
|
|
||||||
}
|
|
||||||
|
|
||||||
* {
|
|
||||||
box-sizing: border-box;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
background: var(--bg);
|
|
||||||
color: var(--text);
|
|
||||||
font-family: monospace, sans-serif;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
max-width: 800px;
|
|
||||||
margin: 20px auto;
|
|
||||||
padding: 0 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo {
|
|
||||||
width: 64px;
|
|
||||||
height: 64px;
|
|
||||||
object-fit: contain;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
font-size: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section-title {
|
|
||||||
font-size: 14px;
|
|
||||||
margin: 20px 0 8px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
.center {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bigstats {
|
|
||||||
display: flex;
|
|
||||||
gap: 20px;
|
|
||||||
justify-content: center;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bigstat {
|
|
||||||
min-width: 120px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bignum {
|
|
||||||
font-size: 32px;
|
|
||||||
font-weight: bold;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.biglabel {
|
|
||||||
font-size: 12px;
|
|
||||||
color: var(--muted);
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stack {
|
|
||||||
display: grid;
|
|
||||||
gap: 8px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#gameIdInput {
|
|
||||||
padding: 8px;
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
background: var(--bg);
|
|
||||||
color: var(--text);
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
padding: 8px;
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
background: #111;
|
|
||||||
color: var(--text);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
button:hover {
|
|
||||||
background: #222;
|
|
||||||
}
|
|
||||||
|
|
||||||
.disclaimer {
|
|
||||||
font-size: 13px;
|
|
||||||
color: var(--muted);
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: var(--accent);
|
|
||||||
}
|
|
||||||
24
README.Docker.md
Normal file
24
README.Docker.md
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
### Building and running your application
|
||||||
|
|
||||||
|
When you're ready, start your application by running:
|
||||||
|
`docker compose up --build`.
|
||||||
|
|
||||||
|
Your application will be available at http://localhost:443.
|
||||||
|
|
||||||
|
### Deploying your application to the cloud
|
||||||
|
|
||||||
|
First, build your image, e.g.: `docker build -t myapp .`.
|
||||||
|
If your cloud uses a different CPU architecture than your development
|
||||||
|
machine (e.g., you are on a Mac M1 and your cloud provider is amd64),
|
||||||
|
you'll want to build the image for that platform, e.g.:
|
||||||
|
`docker build --platform=linux/amd64 -t myapp .`.
|
||||||
|
|
||||||
|
Then, push it to your registry, e.g. `docker push myregistry.com/myapp`.
|
||||||
|
|
||||||
|
Consult Docker's [getting started](https://docs.docker.com/go/get-started-sharing/)
|
||||||
|
docs for more detail on building and pushing.
|
||||||
|
|
||||||
|
### References
|
||||||
|
* [Docker's .NET guide](https://docs.docker.com/language/dotnet/)
|
||||||
|
* The [dotnet-docker](https://github.com/dotnet/dotnet-docker/tree/main/samples)
|
||||||
|
repository has many relevant samples and docs.
|
||||||
51
compose.yaml
Normal file
51
compose.yaml
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# Comments are provided throughout this file to help you get started.
|
||||||
|
# If you need more help, visit the Docker Compose reference guide at
|
||||||
|
# https://docs.docker.com/go/compose-spec-reference/
|
||||||
|
|
||||||
|
# Here the instructions define your application as a service called "server".
|
||||||
|
# This service is built from the Dockerfile in the current directory.
|
||||||
|
# You can add other services your application may depend on here, such as a
|
||||||
|
# database or a cache. For examples, see the Awesome Compose repository:
|
||||||
|
# https://github.com/docker/awesome-compose
|
||||||
|
services:
|
||||||
|
server:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
target: final
|
||||||
|
ports:
|
||||||
|
- 443:8080
|
||||||
|
network_mode: "host"
|
||||||
|
# The commented out section below is an example of how to define a PostgreSQL
|
||||||
|
# database that your application can use. `depends_on` tells Docker Compose to
|
||||||
|
# start the database before your application. The `db-data` volume persists the
|
||||||
|
# database data between container restarts. The `db-password` secret is used
|
||||||
|
# to set the database password. You must create `db/password.txt` and add
|
||||||
|
# a password of your choosing to it before running `docker compose up`.
|
||||||
|
# depends_on:
|
||||||
|
# db:
|
||||||
|
# condition: service_healthy
|
||||||
|
# db:
|
||||||
|
# image: postgres
|
||||||
|
# restart: always
|
||||||
|
# user: postgres
|
||||||
|
# secrets:
|
||||||
|
# - db-password
|
||||||
|
# volumes:
|
||||||
|
# - db-data:/var/lib/postgresql/data
|
||||||
|
# environment:
|
||||||
|
# - POSTGRES_DB=example
|
||||||
|
# - POSTGRES_PASSWORD_FILE=/run/secrets/db-password
|
||||||
|
# expose:
|
||||||
|
# - 5432
|
||||||
|
# healthcheck:
|
||||||
|
# test: [ "CMD", "pg_isready" ]
|
||||||
|
# interval: 10s
|
||||||
|
# timeout: 5s
|
||||||
|
# retries: 5
|
||||||
|
|
||||||
|
# volumes:
|
||||||
|
# db-data:
|
||||||
|
# secrets:
|
||||||
|
# db-password:
|
||||||
|
# file: db/password.txt
|
||||||
|
|
||||||
Reference in New Issue
Block a user