Compare commits

..

27 commits

Author SHA1 Message Date
db7ff97a20
Merge branch 'cswimr/add-src' into CornHusker/add-db 2025-01-01 09:36:28 -05:00
50122f8447
feat: first pass at theming swagger
this is a WIP, will return to this once we have endpoints on the swagger ui
2025-01-01 09:25:21 -05:00
24a4aa7a20
started implementation of web db access. can only create new turrets and view current turrets 2024-12-31 18:31:47 -08:00
21ad7f208a
chore(docker): add dockerfile and docker compose files
also add a `docker` scope to CONTRIBUTING.md
2024-12-31 19:11:36 -05:00
075261de59
chore(repo): add vscode extensions and settings 2024-12-31 19:02:50 -05:00
eb8c304020
chore(tooling): configure appsettings and launchsettings 2024-12-31 19:02:37 -05:00
4a6bca20bc
feat: add sentry 2024-12-31 19:02:22 -05:00
4973464c88
feat: add randomstringgenerator 2024-12-31 19:01:33 -05:00
e0d744ca5a
feat: add configuration model 2024-12-31 19:01:25 -05:00
6ef661f2d4
feat: add swagger ui 2024-12-31 18:40:08 -05:00
a5c5973414
chore(tooling): make vscode open the correct solution 2024-12-31 17:50:09 -05:00
158aec5dba
feat: add description fields to Modules, Spinals, and Turrets 2024-12-31 13:46:29 -05:00
749f827264
feat: add Effect model 2024-12-31 13:44:26 -05:00
132fb649cb
feat: add module model 2024-12-31 13:12:41 -05:00
405032a6e3
refactor: remove this. from the Spinal and Turret models 2024-12-31 12:40:59 -05:00
759e49a64f
feat: add an enum for module rarities 2024-12-31 12:40:12 -05:00
2c3bb51f6b
fix: rename a field in Turret to match what the field is called inside Zenith 2024-12-31 12:32:59 -05:00
0e58107bdd
refactor: remove another useless comment 2024-12-31 12:32:27 -05:00
a126d6415b
fix: fix a bunch of syntax / logic errors
my intellisense and linter wasn't working until the previous commit, where I added the solution file. now it does, and holy shit was everything broken 😭
2024-12-31 12:20:52 -05:00
158451d91f
chore(tooling): add solution file so vscode extensions successfully load when using the nix flake 2024-12-31 12:11:46 -05:00
16e354aa12
refactor: remove a comment
turns out my placeholder values were correct!
2024-12-31 10:48:18 -05:00
645a2d4d5d
refactor: remove a useless import 2024-12-31 10:38:10 -05:00
e66cbcf262
refactor: create a public dictionary for turret damage types 2024-12-31 10:37:41 -05:00
35508c8264
feat: add dps calculation methods to spinals and turrets 2024-12-31 10:33:24 -05:00
56dda7b976
feat: add spinal and turret models 2024-12-31 02:26:53 -05:00
dd614b62b1
style: format Program.cs 2024-12-31 01:03:35 -05:00
a97a181faa
feat: add initial template src 2024-12-31 01:01:16 -05:00
42 changed files with 1541 additions and 1 deletions

View file

@ -3,7 +3,7 @@
"isRoot": true, "isRoot": true,
"tools": { "tools": {
"csharpier": { "csharpier": {
"version": "0.30.6", "version": "0.30.4",
"commands": [ "commands": [
"dotnet-csharpier" "dotnet-csharpier"
] ]

9
.vscode/extensions.json vendored Normal file
View file

@ -0,0 +1,9 @@
{
"recommendations": [
"ms-dotnettools.csharp",
"ms-dotnettools.vscodeintellicode-csharp",
"csharpier.csharpier-vscode",
"patcx.vscode-nuget-gallery",
"vasubasraj.flashpost"
]
}

11
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,11 @@
{
"dotnet.defaultSolution": "ZenithInfo.sln",
"[csharp]": {
"editor.defaultFormatter": "csharpier.csharpier-vscode",
"editor.formatOnSave": true,
"editor.formatOnSaveMode": "file"
},
"files.associations": {
"*.json": "jsonc"
}
}

View file

@ -29,3 +29,4 @@ Please do not use issue identifiers as scopes.
- `tooling`: changes to development tooling like dotnet tools - `tooling`: changes to development tooling like dotnet tools
- `deps`: changes to dependencies, such as updating dependencies or removing unused dependencies - `deps`: changes to dependencies, such as updating dependencies or removing unused dependencies
- `renovate`: changes to the Renovate configuration - `renovate`: changes to the Renovate configuration
- `docker`: changes to the Dockerfile or docker-compose files

20
Components/App.razor Normal file
View file

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<base href="/" />
<link rel="stylesheet" href="bootstrap/bootstrap.min.css" />
<link rel="stylesheet" href="app.css" />
<link rel="stylesheet" href="ZenithInfo.styles.css" />
<link rel="icon" type="image/png" href="favicon.png" />
<HeadOutlet />
</head>
<body>
<Routes />
<script src="_framework/blazor.web.js"></script>
</body>
</html>

View file

@ -0,0 +1,23 @@
@inherits LayoutComponentBase
<div class="page">
<div class="sidebar">
<NavMenu />
</div>
<main>
<div class="top-row px-4">
<a href="https://learn.microsoft.com/aspnet/core/" target="_blank">About</a>
</div>
<article class="content px-4">
@Body
</article>
</main>
</div>
<div id="blazor-error-ui">
An unhandled error has occurred.
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>

View file

@ -0,0 +1,96 @@
.page {
position: relative;
display: flex;
flex-direction: column;
}
main {
flex: 1;
}
.sidebar {
background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
}
.top-row {
background-color: #f7f7f7;
border-bottom: 1px solid #d6d5d5;
justify-content: flex-end;
height: 3.5rem;
display: flex;
align-items: center;
}
.top-row ::deep a, .top-row ::deep .btn-link {
white-space: nowrap;
margin-left: 1.5rem;
text-decoration: none;
}
.top-row ::deep a:hover, .top-row ::deep .btn-link:hover {
text-decoration: underline;
}
.top-row ::deep a:first-child {
overflow: hidden;
text-overflow: ellipsis;
}
@media (max-width: 640.98px) {
.top-row {
justify-content: space-between;
}
.top-row ::deep a, .top-row ::deep .btn-link {
margin-left: 0;
}
}
@media (min-width: 641px) {
.page {
flex-direction: row;
}
.sidebar {
width: 250px;
height: 100vh;
position: sticky;
top: 0;
}
.top-row {
position: sticky;
top: 0;
z-index: 1;
}
.top-row.auth ::deep a:first-child {
flex: 1;
text-align: right;
width: 0;
}
.top-row, article {
padding-left: 2rem !important;
padding-right: 1.5rem !important;
}
}
#blazor-error-ui {
background: lightyellow;
bottom: 0;
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
display: none;
left: 0;
padding: 0.6rem 1.25rem 0.7rem 1.25rem;
position: fixed;
width: 100%;
z-index: 1000;
}
#blazor-error-ui .dismiss {
cursor: pointer;
position: absolute;
right: 0.75rem;
top: 0.5rem;
}

View file

@ -0,0 +1,30 @@
<div class="top-row ps-3 navbar navbar-dark">
<div class="container-fluid">
<a class="navbar-brand" href="">ZenithInfo</a>
</div>
</div>
<input type="checkbox" title="Navigation menu" class="navbar-toggler" />
<div class="nav-scrollable" onclick="document.querySelector('.navbar-toggler').click()">
<nav class="flex-column">
<div class="nav-item px-3">
<NavLink class="nav-link" href="" Match="NavLinkMatch.All">
<span class="bi bi-house-door-fill-nav-menu" aria-hidden="true"></span> Home
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="counter">
<span class="bi bi-plus-square-fill-nav-menu" aria-hidden="true"></span> Counter
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="weather">
<span class="bi bi-list-nested-nav-menu" aria-hidden="true"></span> Weather
</NavLink>
</div>
</nav>
</div>

View file

@ -0,0 +1,105 @@
.navbar-toggler {
appearance: none;
cursor: pointer;
width: 3.5rem;
height: 2.5rem;
color: white;
position: absolute;
top: 0.5rem;
right: 1rem;
border: 1px solid rgba(255, 255, 255, 0.1);
background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e") no-repeat center/1.75rem rgba(255, 255, 255, 0.1);
}
.navbar-toggler:checked {
background-color: rgba(255, 255, 255, 0.5);
}
.top-row {
height: 3.5rem;
background-color: rgba(0,0,0,0.4);
}
.navbar-brand {
font-size: 1.1rem;
}
.bi {
display: inline-block;
position: relative;
width: 1.25rem;
height: 1.25rem;
margin-right: 0.75rem;
top: -1px;
background-size: cover;
}
.bi-house-door-fill-nav-menu {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-house-door-fill' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z'/%3E%3C/svg%3E");
}
.bi-plus-square-fill-nav-menu {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-plus-square-fill' viewBox='0 0 16 16'%3E%3Cpath d='M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z'/%3E%3C/svg%3E");
}
.bi-list-nested-nav-menu {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E");
}
.nav-item {
font-size: 0.9rem;
padding-bottom: 0.5rem;
}
.nav-item:first-of-type {
padding-top: 1rem;
}
.nav-item:last-of-type {
padding-bottom: 1rem;
}
.nav-item ::deep .nav-link {
color: #d7d7d7;
background: none;
border: none;
border-radius: 4px;
height: 3rem;
display: flex;
align-items: center;
line-height: 3rem;
width: 100%;
}
.nav-item ::deep a.active {
background-color: rgba(255,255,255,0.37);
color: white;
}
.nav-item ::deep .nav-link:hover {
background-color: rgba(255,255,255,0.1);
color: white;
}
.nav-scrollable {
display: none;
}
.navbar-toggler:checked ~ .nav-scrollable {
display: block;
}
@media (min-width: 641px) {
.navbar-toggler {
display: none;
}
.nav-scrollable {
/* Never collapse the sidebar for wide screens */
display: block;
/* Allow sidebar to scroll for tall menus */
height: calc(100vh - 3.5rem);
overflow-y: auto;
}
}

View file

@ -0,0 +1,22 @@
@page "/counter"
@rendermode InteractiveServer
<PageTitle>Counter</PageTitle>
<h1>Counter</h1>
<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
@code {
private int currentCount = 0;
[Parameter]
public int IncrementAmount { get; set; } = 1;
private void IncrementCount()
{
currentCount += IncrementAmount;
}
}

View file

@ -0,0 +1,36 @@
@page "/Error"
@using System.Diagnostics
<PageTitle>Error</PageTitle>
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
@if (ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@RequestId</code>
</p>
}
<h3>Development Mode</h3>
<p>
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
</p>
<p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
and restarting the app.
</p>
@code{
[CascadingParameter]
private HttpContext? HttpContext { get; set; }
private string? RequestId { get; set; }
private bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
protected override void OnInitialized() =>
RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier;
}

View file

@ -0,0 +1,9 @@
@page "/"
<PageTitle>Home</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app.
<Counter IncrementAmount="10" />

View file

@ -0,0 +1,104 @@
@page "/turrets/create"
@using System.Runtime.InteropServices.JavaScript
@using Microsoft.EntityFrameworkCore
@using ZenithInfo.Models
@inject IDbContextFactory<ZenithInfo.Data.ZenithInfoContext> DbFactory
@inject NavigationManager NavigationManager
<PageTitle>Create</PageTitle>
<h1>Create</h1>
<h2>Turret</h2>
<hr />
<div class="row">
<div class="col-md-4">
<EditForm method="post" Model="Turret" OnValidSubmit="AddTurret" FormName="create" Enhance>
<DataAnnotationsValidator />
<ValidationSummary class="text-danger" role="alert"/>
<div class="mb-3">
<label for="name" class="form-label">Name:</label>
<InputText id="name" @bind-Value="Turret.Name" class="form-control" />
<ValidationMessage For="() => Turret.Name" class="text-danger" />
</div>
<div class="mb-3">
<label for="description" class="form-label">Description:</label>
<InputText id="description" @bind-Value="Turret.Description" class="form-control" />
<ValidationMessage For="() => Turret.Description" class="text-danger" />
</div>
<div class="mb-3">
<label for="damage" class="form-label">Damage:</label>
<InputNumber id="damage" @bind-Value="Turret.Damage" class="form-control" />
<ValidationMessage For="() => Turret.Damage" class="text-danger" />
</div>
<div class="mb-3">
<label for="reload" class="form-label">Reload:</label>
<InputNumber id="reload" @bind-Value="Turret.Reload" class="form-control" />
<ValidationMessage For="() => Turret.Reload" class="text-danger" />
</div>
<div class="mb-3">
<label for="range" class="form-label">Range:</label>
<InputNumber id="range" @bind-Value="Turret.Range" class="form-control" />
<ValidationMessage For="() => Turret.Range" class="text-danger" />
</div>
<div class="mb-3">
<label for="optimal range" class="form-label">Optimal Range:</label>
<InputNumber id="optimal range" @bind-Value="Turret.OptimalRange" class="form-control" />
<ValidationMessage For="() => Turret.OptimalRange" class="text-danger" />
</div>
<div class="mb-3">
<label for="accuracy falloff range" class="form-label">Accuracy Falloff Range:</label>
<InputNumber id="accuracy falloff range" @bind-Value="Turret.AccuracyFalloffRange" class="form-control" />
<ValidationMessage For="() => Turret.AccuracyFalloffRange" class="text-danger" />
</div>
<div class="mb-3">
<label for="size" class="form-label">Size:</label>
<InputSelect id="size" @bind-Value="Turret.Size" class="form-control" />
<ValidationMessage For="() => Turret.Size" class="text-danger" />
</div>
<div class="mb-3">
<label for="rating" class="form-label">Type:</label>
<InputSelect id="rating" @bind-Value="Turret.Type" class="form-control" />
<ValidationMessage For="() => Turret.Type" class="text-danger" />
</div>
<div class="mb-3">
<label for="icon" class="form-label">Icon:</label>
<InputNumber id="icon" @bind-Value="Turret.Icon" class="form-control" />
<ValidationMessage For="() => Turret.Icon" class="text-danger" />
</div>
<div class="mb-3">
<label for="beam size" class="form-label">Beam Size:</label>
<InputNumber id="beam size" @bind-Value="Turret.BeamSize" class="form-control" />
<ValidationMessage For="() => Turret.BeamSize" class="text-danger" />
</div>
<div class="mb-3">
<label for="base cost" class="form-label">Base Cost:</label>
<InputNumber id="base cost" @bind-Value="Turret.BaseCost" class="form-control" />
<ValidationMessage For="() => Turret.BaseCost" class="text-danger" />
</div>
<div class="mb-3">
<label for="rarity" class="form-label">Rarity:</label>
<InputSelect id="rarity" @bind-Value="Turret.Rarity" class="form-control" />
<ValidationMessage For="() => Turret.Rarity" class="text-danger" />
</div>
<button type="submit" class="btn btn-primary">Create</button>
</EditForm>
</div>
</div>
<div>
<a href="/turrets">Back to List</a>
</div>
@code {
[SupplyParameterFromForm]
private Turret Turret { get; set; } = new();
private async Task AddTurret()
{
using var context = DbFactory.CreateDbContext();
context.Turret.Add(Turret);
await context.SaveChangesAsync();
NavigationManager.NavigateTo("/turrets");
}
}

View file

@ -0,0 +1,64 @@
@page "/turrets"
@rendermode InteractiveServer
@using Microsoft.EntityFrameworkCore
@using Microsoft.AspNetCore.Components.QuickGrid
@using Microsoft.AspNetCore.Components.QuickGrid.EntityFrameworkAdapter
@using ZenithInfo.Models
@using ZenithInfo.Data
@implements IAsyncDisposable
@inject IDbContextFactory<ZenithInfoContext> DbFactory
<PageTitle>Index</PageTitle>
<h1>Index</h1>
<div>
<input type="search" @bind="nameFilter" @bind:event="oninput" />
</div>
<p>
<a href="turrets/create">Create New</a>
</p>
<div>
<QuickGrid Class="table" Items="FilteredTurrets" Pagination="pagination">
<PropertyColumn Property="turret => turret.Name" Sortable="true" />
<PropertyColumn Property="turret => turret.Description" />
<PropertyColumn Property="turret => turret.Damage" />
<PropertyColumn Property="turret => turret.Reload" />
<PropertyColumn Property="turret => turret.Range" />
<PropertyColumn Property="turret => turret.OptimalRange" Title="Optimal Range" />
<PropertyColumn Property="turret => turret.AccuracyFalloffRange" Title="Accuracy Falloff Range" />
<PropertyColumn Property="turret => turret.Accuracy" />
<PropertyColumn Property="turret => turret.Size" />
<PropertyColumn Property="turret => turret.Type" />
<PropertyColumn Property="turret => turret.Icon" />
<PropertyColumn Property="turret => turret.BeamSize" />
<PropertyColumn Property="turret => turret.BaseCost" />
<PropertyColumn Property="turret => turret.Rarity" />
<TemplateColumn Context="turret">
<a href="@($"turrets/edit?id={turret.Id}")">Edit</a> |
<a href="@($"turrets/details?id={turret.Id}")">Details</a> |
<a href="@($"turrets/delete?id={turret.Id}")">Delete</a>
</TemplateColumn>
</QuickGrid>
</div>
<Paginator State="pagination" />
@code {
private ZenithInfoContext context = default!;
private PaginationState pagination = new PaginationState { ItemsPerPage = 5 };
private string nameFilter = string.Empty;
private IQueryable<Turret> FilteredTurrets =>
context.Turret.Where(m => m.Name!.Contains(nameFilter));
protected override void OnInitialized()
{
context = DbFactory.CreateDbContext();
}
public async ValueTask DisposeAsync() => await context.DisposeAsync();
}

View file

@ -0,0 +1,7 @@
::deep tr {
height: 3em;
}
::deep tr > td {
vertical-align: middle;
}

View file

@ -0,0 +1,64 @@
@page "/weather"
@attribute [StreamRendering]
<PageTitle>Weather</PageTitle>
<h1>Weather</h1>
<p>This component demonstrates showing data.</p>
@if (forecasts == null)
{
<p><em>Loading...</em></p>
}
else
{
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
@foreach (var forecast in forecasts)
{
<tr>
<td>@forecast.Date.ToShortDateString()</td>
<td>@forecast.TemperatureC</td>
<td>@forecast.TemperatureF</td>
<td>@forecast.Summary</td>
</tr>
}
</tbody>
</table>
}
@code {
private WeatherForecast[]? forecasts;
protected override async Task OnInitializedAsync()
{
// Simulate asynchronous loading to demonstrate streaming rendering
await Task.Delay(500);
var startDate = DateOnly.FromDateTime(DateTime.Now);
var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" };
forecasts = Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = startDate.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = summaries[Random.Shared.Next(summaries.Length)]
}).ToArray();
}
private class WeatherForecast
{
public DateOnly Date { get; set; }
public int TemperatureC { get; set; }
public string? Summary { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
}

6
Components/Routes.razor Normal file
View file

@ -0,0 +1,6 @@
<Router AppAssembly="typeof(Program).Assembly">
<Found Context="routeData">
<RouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)" />
<FocusOnNavigate RouteData="routeData" Selector="h1" />
</Found>
</Router>

10
Components/_Imports.razor Normal file
View file

@ -0,0 +1,10 @@
@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using static Microsoft.AspNetCore.Components.Web.RenderMode
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using ZenithInfo
@using ZenithInfo.Components

19
Data/ZenithInfoContext.cs Normal file
View file

@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using ZenithInfo.Models;
namespace ZenithInfo.Data
{
public class ZenithInfoContext : DbContext
{
public ZenithInfoContext (DbContextOptions<ZenithInfoContext> options)
: base(options)
{
}
public DbSet<ZenithInfo.Models.Turret> Turret { get; set; } = default!;
}
}

23
Dockerfile Normal file
View file

@ -0,0 +1,23 @@
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
USER $APP_UID
WORKDIR /app
EXPOSE 8080
EXPOSE 8081
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["ZenithInfo.csproj", "./"]
RUN dotnet restore "ZenithInfo.csproj"
COPY . .
WORKDIR "/src/"
RUN dotnet build "ZenithInfo.csproj" -c $BUILD_CONFIGURATION -o /app/build
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "ZenithInfo.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "ZenithInfo.dll"]

19
Models/DamageStats.cs Normal file
View file

@ -0,0 +1,19 @@
namespace ZenithInfo.Models
{
public class DamageStats(
double averageDps,
double shieldDps,
double hullDps,
double averageAlpha,
double shieldAlpha,
double hullAlpha
)
{
public double AverageDps { get; set; } = averageDps;
public double ShieldDps { get; set; } = shieldDps;
public double HullDps { get; set; } = hullDps;
public double AverageAlpha { get; set; } = averageAlpha;
public double ShieldAlpha { get; set; } = shieldAlpha;
public double HullAlpha { get; set; } = hullAlpha;
}
}

41
Models/Effect.cs Normal file
View file

@ -0,0 +1,41 @@
using System.Collections.Generic;
namespace ZenithInfo.Models
{
public class Effect
{
public required string Name { get; set; }
public Enums.ModuleEffectTypes Type { get; set; }
public double Duration { get; set; }
public double ApplyDelta { get; set; }
public Enums.IsBuffed IsBuff { get; set; }
public bool ClearApplyOnDispel { get; set; }
public required ApplyEffect Apply { get; set; }
public required TickEffect Tick { get; set; }
public required DispelEffect Dispel { get; set; }
public required List<string> DisplayEffect { get; set; }
public class ApplyEffect
{
public required Dictionary<string, double> Stats { get; set; }
private object? Other { get; set; }
public required List<string> Flags { get; set; }
public bool HasOther()
{
return Other != null;
}
}
public class TickEffect
{
public required Dictionary<string, double> Stats { get; set; }
}
public class DispelEffect
{
public required Dictionary<string, double> Stats { get; set; }
}
}
}

85
Models/Enums.cs Normal file
View file

@ -0,0 +1,85 @@
using System.Collections.Generic;
namespace ZenithInfo.Models
{
public static class Enums
{
public enum ModuleSizes
{
All,
Small,
Medium,
Large,
Capital,
MiningSmall,
MiningMedium,
MiningCapital,
SmallBase,
MediumBase,
LargeBase,
}
public enum ModuleTypes
{
Passive,
Toggle,
Active,
AoE,
}
public enum Rarities
{
Common,
Rare,
Scare,
Unique,
}
public enum DamageTypes
{
Shield,
Hull,
}
public enum SpinalTypes
{
Hitscan,
Multishot,
}
public enum TurretTypes
{
Artillery,
Autocannon,
BeamLaser,
Blaster,
Railgun,
PulseLaser,
}
public static Dictionary<TurretTypes, DamageTypes> TurretDamageTypes { get; } =
new Dictionary<TurretTypes, DamageTypes>
{
{ TurretTypes.Artillery, DamageTypes.Hull },
{ TurretTypes.Autocannon, DamageTypes.Hull },
{ TurretTypes.BeamLaser, DamageTypes.Shield },
{ TurretTypes.Blaster, DamageTypes.Shield },
{ TurretTypes.Railgun, DamageTypes.Hull },
{ TurretTypes.PulseLaser, DamageTypes.Shield },
};
public enum ModuleEffectTypes
{
Duration,
Persistent,
AoE,
}
public enum IsBuffed
{
True,
False,
Self,
}
}
}

20
Models/Module.cs Normal file
View file

@ -0,0 +1,20 @@
namespace ZenithInfo.Models
{
public class Module
{
public required string Name { get; set; }
public required string Description { get; set; }
public Enums.ModuleTypes Type { get; set; }
public bool IsTargeted { get; set; }
public required List<string> Effects { get; set; }
public double Cooldown { get; set; }
public double Duration { get; set; }
public Enums.ModuleSizes Size { get; set; }
public double Range { get; set; }
public int MaxTargets { get; set; }
public double EnergyUsage { get; set; }
public int Icon { get; set; }
public required Dictionary<string, object> Stats { get; set; }
public double BaseCost { get; set; }
}
}

64
Models/Spinal.cs Normal file
View file

@ -0,0 +1,64 @@
namespace ZenithInfo.Models
{
public class Spinal
{
public required string Name { get; set; }
public required string Description { get; set; }
public double Damage { get; set; }
public double Reload { get; set; }
public double Range { get; set; }
public Enums.SpinalTypes Type { get; set; }
public Enums.DamageTypes DamageType { get; set; }
public Enums.ModuleSizes Size { get; set; }
public double Velocity { get; set; }
public double Interval { get; set; }
public int Icon { get; set; }
public double ParticleSize { get; set; }
public double BaseCost { get; set; }
public Enums.Rarities Rarity { get; set; }
public DamageStats Dps(int range = 0, int barrelCount = 1)
{
if (range > Range)
{
return new DamageStats(0, 0, 0, 0, 0, 0);
}
double baseAlpha = Damage * barrelCount;
double shieldAlpha = 0;
double hullAlpha = 0;
double intervalTime = Interval * (barrelCount - 1);
double totalDelay = Reload + intervalTime;
double baseDps = baseAlpha / (totalDelay != 0 ? totalDelay : 1); // prevent divide by zero
double shieldDps = 0;
double hullDps = 0;
if (DamageType == Enums.DamageTypes.Shield)
{
shieldAlpha = baseAlpha;
hullAlpha = baseAlpha * 0.5;
shieldDps = baseDps;
hullDps = baseDps * 0.5;
}
else if (DamageType == Enums.DamageTypes.Hull)
{
shieldAlpha = baseAlpha * 0.5;
hullAlpha = baseAlpha;
shieldDps = baseDps * 0.5;
hullDps = baseDps;
}
double averageDps = (shieldDps + hullDps) / 2;
double averageAlpha = (shieldAlpha + hullAlpha) / 2;
return new DamageStats(
averageDps,
shieldDps,
hullDps,
averageAlpha,
shieldAlpha,
hullAlpha
);
}
}
}

72
Models/Turret.cs Normal file
View file

@ -0,0 +1,72 @@
using System.Collections.Generic;
namespace ZenithInfo.Models
{
public class Turret
{
// required by EF Core. see https://learn.microsoft.com/en-us/aspnet/core/blazor/tutorials/movie-database-app/part-2?view=aspnetcore-9.0&pivots=vs#add-a-data-model
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public double Damage { get; set; }
public double Reload { get; set; }
public double Range { get; set; }
public double OptimalRange { get; set; }
public double AccuracyFalloffRange { get; set; }
public double Accuracy { get; set; }
public Enums.ModuleSizes Size { get; set; }
public Enums.TurretTypes Type { get; set; }
public int Icon { get; set; }
public double BeamSize { get; set; }
public double BaseCost { get; set; }
public Enums.Rarities Rarity { get; set; }
public Enums.DamageTypes DamageType()
{
return Enums.TurretDamageTypes[Type];
}
public DamageStats Dps(int range = 0)
{
if (range > Range)
{
return new DamageStats(0, 0, 0, 0, 0, 0);
}
double baseAlpha = Damage;
double shieldAlpha = 0;
double hullAlpha = 0;
double baseDps = baseAlpha / (Reload != 0 ? Reload : 1); // prevent divide by zero
double shieldDps = 0;
double hullDps = 0;
if (DamageType() == Enums.DamageTypes.Shield)
{
shieldAlpha = baseAlpha;
hullAlpha = baseAlpha * 0.5;
shieldDps = baseDps;
hullDps = baseDps * 0.5;
}
else if (DamageType() == Enums.DamageTypes.Hull)
{
shieldAlpha = baseAlpha * 0.5;
hullAlpha = baseAlpha;
shieldDps = baseDps * 0.5;
hullDps = baseDps;
}
double averageDps = (shieldDps + hullDps) / 2;
double averageAlpha = (shieldAlpha + hullAlpha) / 2;
return new DamageStats(
averageDps,
shieldDps,
hullDps,
averageAlpha,
shieldAlpha,
hullAlpha
);
}
}
}

18
Models/ZIConfiguration.cs Normal file
View file

@ -0,0 +1,18 @@
namespace ZenithInfo.Models
{
public class ZIConfiguration
{
public required string GlobalToken { get; init; }
public string? DatabaseConnectionString { get; init; } = null;
public DatabaseTypes DatabaseType { get; init; } = DatabaseTypes.Memory;
public bool UseSentry { get; init; } = true;
public bool UseSwagger { get; init; } = true;
public bool LogRequests { get; init; } = true;
}
public enum DatabaseTypes
{
Memory,
PostgresSQL,
}
}

135
Program.cs Normal file
View file

@ -0,0 +1,135 @@
using System.Reflection;
using Microsoft.AspNetCore.Components.QuickGrid.EntityFrameworkAdapter;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OpenApi.Models;
using ZenithInfo.Components;
using ZenithInfo.Data;
using ZenithInfo.Models;
using ZenithInfo.Util.SwaggerTheme;
var builder = WebApplication.CreateBuilder(args);
// Configuration
var config = builder.Configuration.GetSection("Configuration").Get<ZIConfiguration>();
builder.Services.Configure<ZIConfiguration>(builder.Configuration.GetSection("Configuration"));
if (config?.GlobalToken == "CHANGE-ME")
{
throw new InvalidOperationException(
"Global token is not a valid value, please set the `CONFIGURATION__GLOBALTOKEN` environment variable to a random value. `openssl rand -hex 64` is a good way to generate a random value."
);
}
// Initialize Database
builder.Services.AddQuickGridEntityFrameworkAdapter();
builder.Services.AddDbContextFactory<ZenithInfoContext>(options =>
options.UseNpgsql(builder.Configuration.GetConnectionString("ZenithInfoContext"))
);
// Sentry
if (config?.UseSentry == true)
{
builder.WebHost.UseSentry();
}
// Add services to the container.
builder.Services.AddRazorComponents().AddInteractiveServerComponents();
builder.Services.AddHttpLogging(o => { });
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(opts =>
{
opts.SwaggerDoc(
"v1",
new OpenApiInfo
{
Title = "Zenith Info API",
Version = "v1",
Description = "Zenith's official information provider!",
}
);
opts.AddSecurityDefinition(
"Bearer",
new OpenApiSecurityScheme
{
Description =
"JWT Authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\"",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey,
Scheme = "Bearer",
}
);
opts.AddSecurityRequirement(
new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer",
},
Scheme = "oauth2",
Name = "Bearer",
In = ParameterLocation.Header,
},
new List<string>()
},
}
);
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
opts.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFile));
});
var app = builder.Build();
var assembly = Assembly.GetExecutingAssembly();
foreach (var resource in assembly.GetManifestResourceNames())
{
app.Logger.LogDebug("Resource: {Resource}", resource);
}
if (config?.UseSwagger == true)
{
app.UseSwagger();
app.UseSwaggerUI(CustomStyle.CustomModern);
}
if (config?.LogRequests == true)
{
app.UseHttpLogging();
}
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error", createScopeForErrors: true);
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAntiforgery();
app.MapRazorComponents<App>().AddInteractiveServerRenderMode();
if (config?.DatabaseType == DatabaseTypes.Memory)
{
app.Logger.LogWarning(
"`Memory` database type is in use. Data will be lost when the application is restarted. If you are not in a development environment, you should use `PostgresSQL` instead."
);
}
// if (app.Environment.IsDevelopment())
// {
// app.Logger.LogInformation(
// "Development mode detected, printing configuration: {Configuration}",
// config?.ToJson()
// );
// }
app.Run();

View file

@ -0,0 +1,41 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:48070",
"sslPort": 44382
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "http://localhost:5150",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:7047;http://localhost:5150",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View file

@ -0,0 +1,26 @@
using System.Security.Cryptography;
using System.Text;
namespace ZenithInfo.Util;
public class RandomStringGenerator
{
private static readonly char[] chars =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*+?".ToCharArray();
public static string GenerateRandomString(int length)
{
var stringBuilder = new StringBuilder();
using (var rng = RandomNumberGenerator.Create())
{
byte[] buffer = new byte[1];
for (int i = 0; i < length; i++)
{
rng.GetBytes(buffer);
var randomIndex = buffer[0] % chars.Length;
stringBuilder.Append(chars[randomIndex]);
}
}
return stringBuilder.ToString();
}
}

View file

@ -0,0 +1,12 @@
using System;
using AspNetCore.Swagger.Themes;
namespace ZenithInfo.Util.SwaggerTheme;
public class CustomStyle : ModernStyle
{
protected CustomStyle(string fileName)
: base(fileName) { }
public static CustomStyle CustomModern => new("modern.custom.css");
}

View file

@ -0,0 +1,117 @@
:root {
/* Light mode (Catppuccin Latte) */
--catppuccin-pink: #ea76cb;
--catppuccin-mauve: #8839ef;
--catppuccin-red: #d20f39;
--catppuccin-maroon: #e64553;
--catppuccin-peach: #fe640b;
--catppuccin-yellow: #df8e1d;
--catppuccin-green: #40a02b;
--catppuccin-teal: #179299;
--catppuccin-sky: #04a5e5;
--catppuccin-sapphire: #209fb5;
--catppuccin-blue: #1e66f5;
--catppuccin-lavender: #7287fd;
--catppuccin-text: #4c4f69;
--catppuccin-subtext1: #5c5f77;
--catppuccin-subtext0: #6c6f85;
--catppuccin-overlay2: #7c7f93;
--catppuccin-overlay1: #8c8fa1;
--catppuccin-overlay0: #9ca0b0;
--catppuccin-surface2: #acb0be;
--catppuccin-surface1: #bcc0cc;
--catppuccin-surface0: #ccd0da;
--catppuccin-base: #eff1f5;
--catppuccin-mantle: #e6e9ef;
--catppuccin-crust: #dce0e8;
--link: var(--catppuccin-blue);
--link-hover: var(--catppuccin-sapphire);
--accent: var(--catppuccin-green);
--error: var(--catppuccin-red);
/* Body */
--body-background-color: var(--catppuccin-base);
--swagger-main-color: var(--catppuccin-text);
--scrollbar-thumb-color: var(--catppuccin-surface1);
--scrollbar-thumb-hover-color: var(--catppuccin-surface0);
/* Loading */
--loading-container-border-color: var(--accent);
/* Errors Container */
--errors-wrapper-background-color: var(--catppuccin-surface0);
--errors-wrapper-border-color: var(--error);
--errors-wrapper-errors-color: var(--error);
/* Topbar */
--topbar-background-color: var(--catppuccin-surface0);
--topbar-pinned-background-color: var(--catppuccin-surface0);
--topbar-border-color: var(--accent);
--topbar-select-label-color: var(--catppuccin-text);
--topbar-select-border-color: var(--accent);
/* Infobox */
--swagger-info-link: var(--link);
--swagger-info-link-hover: var(--link-hover);
--api-version-background-color: var(--catppuccin-surface2);
--api-version-stamp-background-color: var(--accent);
--api-version-color: var(--catppuccin-base);
/* Authorization */
--btn-authorize-background-color: var(--catppuccin-surface0);
--btn-authorize-border-color: var(--catppuccin-green);
--btn-authorize-font-color: var(--catppuccin-green);
--btn-authorize-svg-fill-color: var(--catppuccin-green);
--auth-container-background-color: var(--catppuccin-surface0);
--auth-container-errors-color: var(--catppuccin-red);
--auth-wrapper-background-color: var(--catppuccin-mantle);
--auth-wrapper-border-color: var(--accent);
/* Form Elements */
--select-border-color: var(--accent);
--input-background-color: var(--catppuccin-surface0);
--input-border-color: var(--accent);
--input-invalid-border-color: var(--error);
--input-disabled-background-color: var(--catppuccin-crust);
--input-disabled-border-color: var(--catppuccin-surface1);
/* Dialog */
--dialog-background-color: var(--catppuccin-mantle);
--dialog-border-color: var(--accent);
}
.swagger-ui .topbar .download-url-wrapper input[type="text"], .swagger-ui .topbar .download-url-wrapper .select-label select {
background-color: var(--catppuccin-surface0);
color: var(--catppuccin-text);
}
@media (prefers-color-scheme: dark) {
:root {
/* Dark mode (Catppuccin Mocha) */
--catppuccin-pink: #f5c2e7;
--catppuccin-mauve: #cba6f7;
--catppuccin-red: #f38ba8;
--catppuccin-maroon: #eba0ac;
--catppuccin-peach: #fab387;
--catppuccin-yellow: #f9e2af;
--catppuccin-green: #a6e3a1;
--catppuccin-teal: #94e2d5;
--catppuccin-sky: #89dceb;
--catppuccin-sapphire: #74c7ec;
--catppuccin-blue: #89b4fa;
--catppuccin-lavender: #b4befe;
--catppuccin-text: #cdd6f4;
--catppuccin-subtext1: #bac2de;
--catppuccin-subtext0: #a6adc8;
--catppuccin-overlay2: #9399b2;
--catppuccin-overlay1: #7f849c;
--catppuccin-overlay0: #6c7086;
--catppuccin-surface2: #585b70;
--catppuccin-surface1: #45475a;
--catppuccin-surface0: #313244;
--catppuccin-base: #1e1e2e;
--catppuccin-mantle: #181825;
--catppuccin-crust: #11111b;
}
}

41
ZenithInfo.csproj Normal file
View file

@ -0,0 +1,41 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="AspNetCore.SwaggerUI.Themes" Version="2.0.0" />
<PackageReference Include="Sentry.AspNetCore" Version="5.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="7.2.0" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.11" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="9.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="9.0.0" />
<PackageReference Include="Npgsql" Version="9.0.2" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.2" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.Design" Version="1.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.QuickGrid.EntityFrameworkAdapter" Version="8.0.0" />
</ItemGroup>
<ItemGroup>
<Folder Include="Data\" />
<Folder Include="Migrations\" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Util\SwaggerTheme\modern.custom.css" />
</ItemGroup>
</Project>

22
ZenithInfo.sln Normal file
View file

@ -0,0 +1,22 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZenithInfo", "ZenithInfo.csproj", "{49ED62A1-936C-41EC-949D-D490C8F45711}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{49ED62A1-936C-41EC-949D-D490C8F45711}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{49ED62A1-936C-41EC-949D-D490C8F45711}.Debug|Any CPU.Build.0 = Debug|Any CPU
{49ED62A1-936C-41EC-949D-D490C8F45711}.Release|Any CPU.ActiveCfg = Release|Any CPU
{49ED62A1-936C-41EC-949D-D490C8F45711}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

View file

@ -0,0 +1,12 @@
{
"Logging": {
"LogLevel": {
"Default": "Trace"
}
},
"Configuration": {
// DO NOT USE THIS AUTHTOKEN IN PRODUCTION!!!
"AuthToken": "hi",
"UseSentry": false
}
}

27
appsettings.json Normal file
View file

@ -0,0 +1,27 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning",
"Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware": "Information"
}
},
"AllowedHosts": "*",
"Configuration": {
"AuthToken": "CHANGE-ME",
"UseSwagger": true,
"LogRequests": true,
"UseSentry": true
},
"Sentry": {
"Dsn": "https://92b5c863371d9362c50a39b3faccd26d@sentry.csw.im/5",
"SendDefaultPii": true,
"MaxRequestBodySize": "Always",
"MinimumBreadcrumbLevel": "Debug",
"MinimumEventLevel": "Warning",
"AttachStackTrace": true,
"Debug": true,
"DiagnosticLevel": "Error",
"TracesSampleRate": 1.0
}
}

13
docker-compose.dev.yml Normal file
View file

@ -0,0 +1,13 @@
services:
zenithinfo-dev:
container_name: zenithinfo
extends:
service: zenithinfo
file: ./docker-compose.yml
build:
context: .
environment:
CONFIGURATION__AUTHTOKEN: 'hi'
CONFIGURATION__DATABASETYPE: 'Memory'
LOGGING__LOGLEVEL_DEFAULT: 'Trace'
CONFIGURATION__USESENTRY: false

57
docker-compose.yml Normal file
View file

@ -0,0 +1,57 @@
services:
zenithinfo:
image: www.coastalcommits.com/zenith/zenithinfo:latest
ports:
- 8000:8000
environment:
# All of the environment variables present here use double underscores (__), NOT single underscores (_).
# The only exception is the `ASPNETCORE_URLS` environment variable, which uses single underscores.
# This is used to push data into Zenith Info from the game.
# `openssl rand -hex 64` is a good way to generate a random value. DO NOT CHECK THIS INTO GIT!
CONFIGURATION__AUTHTOKEN: 'CHANGE-ME'
# This is used to configure the IP address and port that the application will listen on.
# 0.0.0.0 is used to listen on all available IP addresses.
# Alternatively, you can specify localhost or 127.0.0.1 to listen on only the local machine.
# Multiple IP address / port combinations can be specified, separated by semicolons.
# Example: http://0.0.0.0:8000;http://127.0.0.1:8000 - Listen on all available IP addresses on port 8000, as well as only on the local machine on port 8000. (Redundant)
ASPNETCORE_URLS: 'http://0.0.0.0:8000'
# Valid values are `Memory` or `PostgresSQL`. This is used to determine which database to use.
# `Memory` is used for development and testing, and is not recommended for production, as data is not persistent.
CONFIGURATION__DATABASETYPE: 'PostgresSQL'
CONFIGURATION__DATABASECONNECTIONSTRING: 'postgres://zenithinfo:zenithinfo@postgres:5432/zenithinfo'
# Determines the log level for the application's default logger.
# Valid values are `Trace`, `Debug`, `Information`, `Warning`, `Error`, and `Critical`.
# `Trace` is the most verbose, and is recommended for development.
# `Information` is the default, and is recommended for production.
LOGGING__LOGLEVEL__DEFAULT: 'Information'
# This is used to log requests to the application.
# If you are concerned about security or privacy, you should disable this.
CONFIGURATION__LOGREQUESTS: true
# Using Sentry will send errors to my personal Sentry instance, https://sentry.csw.im/
# This is not required for the application to run, but it is recommended for debugging purposes.
CONFIGURATION__USESENTRY: true
# This is used to enable Swagger API Documentation for the application.
# If enabled, you can access the API documentation at the /swagger/index.html endpoint.
CONFIGURATION__USESWAGGER: true
database:
image: postgres:15.4
restart: always
environment:
POSTGRES_USER: zenithinfo
POSTGRES_PASSWORD: zenithinfo
POSTGRES_DB: zenithinfo
ports:
- 5433:5432
volumes:
- postgres-data:/var/lib/postgresql/data
volumes:
postgres-data:

51
wwwroot/app.css Normal file
View file

@ -0,0 +1,51 @@
html, body {
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
}
a, .btn-link {
color: #006bb7;
}
.btn-primary {
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
}
.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
}
.content {
padding-top: 1.1rem;
}
h1:focus {
outline: none;
}
.valid.modified:not([type=checkbox]) {
outline: 1px solid #26b050;
}
.invalid {
outline: 1px solid #e50000;
}
.validation-message {
color: #e50000;
}
.blazor-error-boundary {
background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
padding: 1rem 1rem 1rem 3.7rem;
color: white;
}
.blazor-error-boundary::after {
content: "An error has occurred."
}
.darker-border-checkbox.form-check-input {
border-color: #929292;
}

7
wwwroot/bootstrap/bootstrap.min.css vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

BIN
wwwroot/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB