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
|
||||
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 DisplayName { get; init; }
|
||||
public int userPing { 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<ILobbyService, LobbyService>();
|
||||
builder.Services.AddRazorPages(); // Register Razor Pages services
|
||||
var app = builder.Build();
|
||||
if (!app.Environment.IsDevelopment())
|
||||
{
|
||||
@@ -102,10 +103,10 @@ app.UseResponseCompression();
|
||||
app.UseHttpLogging();
|
||||
app.UseRateLimiter();
|
||||
app.UseWebSockets();
|
||||
app.UseDefaultFiles();
|
||||
app.UseStaticFiles();
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI();
|
||||
app.MapRazorPages(); // Enable Razor Pages
|
||||
static CookieOptions BuildStdCookieOptions() => new()
|
||||
{
|
||||
HttpOnly = true,
|
||||
@@ -506,4 +507,4 @@ public record CreateLobbyRequest(string OwnerDisplayName, int MaxPlayers, Dictio
|
||||
public record JoinLobbyRequest(string DisplayName);
|
||||
public record ReadyRequest(string UserId, bool IsReady);
|
||||
public record AllReadyRequest(string UserId);
|
||||
public record LobbyDataRequest(string Key, string Value);
|
||||
public record LobbyDataRequest(string Key, string Value);
|
||||
@@ -26,4 +26,14 @@
|
||||
</Content>
|
||||
</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>
|
||||
|
||||
@@ -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