1
0
mirror of https://github.com/chylex/Minecraft-Phantom-Panel.git synced 2025-09-16 00:32:12 +02:00

3 Commits

Author SHA1 Message Date
591a6a62ab Reformat code 2025-08-21 20:31:21 +02:00
ae32537d8c Update to .NET 9 and C# 13 2025-08-08 22:04:41 +02:00
8149d31d51 Update Java versions in Dockerfile 2024-07-14 10:40:36 +02:00
252 changed files with 1673 additions and 1668 deletions

View File

@@ -105,7 +105,7 @@ public abstract class BaseLauncher : IServerLauncher {
private static async Task AcceptEula(InstanceProperties instanceProperties) {
var eulaFilePath = Path.Combine(instanceProperties.InstanceFolder, "eula.txt");
await File.WriteAllLinesAsync(eulaFilePath, new [] { "# EULA", "eula=true" }, Encoding.UTF8);
await File.WriteAllLinesAsync(eulaFilePath, new[] { "# EULA", "eula=true" }, Encoding.UTF8);
}
private static async Task UpdateServerProperties(InstanceProperties instanceProperties) {

View File

@@ -5,7 +5,6 @@ using Phantom.Common.Data.Instance;
using Phantom.Common.Data.Replies;
using Phantom.Common.Messages.Agent.ToController;
using Phantom.Utils.Logging;
using Phantom.Utils.Tasks;
using Serilog;
namespace Phantom.Agent.Services.Instances;

View File

@@ -8,7 +8,7 @@ namespace Phantom.Common.Data;
public sealed partial class AllowedPorts {
[MemoryPackOrder(0)]
[MemoryPackInclude]
private readonly ImmutableArray<PortRange> allDefinitions;
private readonly ImmutableArray<PortRange> allDefinitions;
private AllowedPorts(ImmutableArray<PortRange> allDefinitions) {
// TODO normalize and deduplicate ranges

View File

@@ -83,7 +83,7 @@ public readonly partial record struct RamAllocationUnits(
int unitMultiplier = char.ToUpperInvariant(definition[^1]) switch {
'M' => 1,
'G' => 1024,
_ => throw new ArgumentOutOfRangeException(nameof(definition), "Must end with 'M' or 'G'.")
_ => throw new ArgumentOutOfRangeException(nameof(definition), "Must end with 'M' or 'G'.")
};
if (!int.TryParse(definition[..^1], out int size)) {

View File

@@ -1,6 +1,7 @@
using System.Collections.Immutable;
using Phantom.Common.Data;
using Phantom.Common.Data.Web.Users;
using Phantom.Common.Data.Web.Users.CreateOrUpdateAdministratorUserResults;
using Phantom.Controller.Database;
using Phantom.Controller.Database.Entities;
using Phantom.Controller.Database.Repositories;
@@ -56,12 +57,12 @@ sealed class UserManager {
wasCreated = true;
}
else {
return new Common.Data.Web.Users.CreateOrUpdateAdministratorUserResults.CreationFailed(result.Error);
return new CreationFailed(result.Error);
}
}
else {
if (userRepository.SetUserPassword(user, password).TryGetError(out var error)) {
return new Common.Data.Web.Users.CreateOrUpdateAdministratorUserResults.UpdatingFailed(error);
return new UpdatingFailed(error);
}
auditLogWriter.AdministratorUserModified(user);
@@ -70,7 +71,7 @@ sealed class UserManager {
var role = await new RoleRepository(db).GetByGuid(Role.Administrator.Guid);
if (role == null) {
return new Common.Data.Web.Users.CreateOrUpdateAdministratorUserResults.AddingToRoleFailed();
return new AddingToRoleFailed();
}
await new UserRoleRepository(db).Add(user, role);
@@ -84,10 +85,10 @@ sealed class UserManager {
Logger.Information("Updated administrator user \"{Username}\" (GUID {Guid}).", username, user.UserGuid);
}
return new Common.Data.Web.Users.CreateOrUpdateAdministratorUserResults.Success(user.ToUserInfo());
return new Success(user.ToUserInfo());
} catch (Exception e) {
Logger.Error(e, "Could not create or update administrator user \"{Username}\".", username);
return new Common.Data.Web.Users.CreateOrUpdateAdministratorUserResults.UnknownError();
return new UnknownError();
}
}

View File

@@ -1,8 +1,8 @@
<Project>
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<LangVersion>11</LangVersion>
<TargetFramework>net9.0</TargetFramework>
<LangVersion>13</LangVersion>
</PropertyGroup>
<PropertyGroup>

View File

@@ -1,7 +1,7 @@
# +---------------+
# | Prepare build |
# +---------------+
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/nightly/sdk:8.0 AS phantom-builder
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/nightly/sdk:9.0 AS phantom-builder
ARG TARGETARCH
ADD . /app
@@ -19,7 +19,7 @@ RUN find .artifacts/publish/*/* -maxdepth 0 -execdir mv '{}' 'release' \;
# +---------------------+
# | Phantom Agent image |
# +---------------------+
FROM mcr.microsoft.com/dotnet/nightly/runtime:8.0 AS phantom-agent
FROM mcr.microsoft.com/dotnet/nightly/runtime:9.0 AS phantom-agent
RUN mkdir /data && chmod 777 /data
WORKDIR /data
@@ -27,7 +27,7 @@ WORKDIR /data
COPY --from=eclipse-temurin:8-jre /opt/java/openjdk /opt/java/8
COPY --from=eclipse-temurin:16-jdk /opt/java/openjdk /opt/java/16
COPY --from=eclipse-temurin:17-jre /opt/java/openjdk /opt/java/17
COPY --from=eclipse-temurin:20-jre /opt/java/openjdk /opt/java/20
COPY --from=eclipse-temurin:21-jre /opt/java/openjdk /opt/java/21
ARG DEBIAN_FRONTEND=noninteractive
@@ -46,7 +46,7 @@ ENTRYPOINT ["dotnet", "/app/Phantom.Agent.dll"]
# +--------------------------+
# | Phantom Controller image |
# +--------------------------+
FROM mcr.microsoft.com/dotnet/nightly/runtime:8.0 AS phantom-controller
FROM mcr.microsoft.com/dotnet/nightly/runtime:9.0 AS phantom-controller
RUN mkdir /data && chmod 777 /data
WORKDIR /data
@@ -59,7 +59,7 @@ ENTRYPOINT ["dotnet", "/app/Phantom.Controller.dll"]
# +-------------------+
# | Phantom Web image |
# +-------------------+
FROM mcr.microsoft.com/dotnet/nightly/aspnet:8.0 AS phantom-web
FROM mcr.microsoft.com/dotnet/nightly/aspnet:9.0 AS phantom-web
RUN mkdir /data && chmod 777 /data
WORKDIR /data

View File

@@ -59,25 +59,25 @@ public sealed class RingBufferTests {
[Test]
public void AddOneItemAndEnumerateOne() {
var buffer = PrepareRingBuffer(10, "a");
Assert.That(buffer.EnumerateLast(1), Is.EquivalentTo(new [] { "a" }));
Assert.That(buffer.EnumerateLast(1), Is.EquivalentTo(new[] { "a" }));
}
[Test]
public void AddOneItemAndEnumerateMaxValue() {
var buffer = PrepareRingBuffer(10, "a");
Assert.That(buffer.EnumerateLast(uint.MaxValue), Is.EquivalentTo(new [] { "a" }));
Assert.That(buffer.EnumerateLast(uint.MaxValue), Is.EquivalentTo(new[] { "a" }));
}
[Test]
public void AddMultipleItemsWithinCapacityAndEnumerateFewer() {
var buffer = PrepareRingBuffer(10, "a", "b", "c");
Assert.That(buffer.EnumerateLast(2), Is.EquivalentTo(new [] { "b", "c" }));
Assert.That(buffer.EnumerateLast(2), Is.EquivalentTo(new[] { "b", "c" }));
}
[Test]
public void AddMultipleItemsWithinCapacityAndEnumerateMaxValue() {
var buffer = PrepareRingBuffer(10, "a", "b", "c");
Assert.That(buffer.EnumerateLast(uint.MaxValue), Is.EquivalentTo(new [] { "a", "b", "c" }));
Assert.That(buffer.EnumerateLast(uint.MaxValue), Is.EquivalentTo(new[] { "a", "b", "c" }));
}
[TestCase(3)]
@@ -85,12 +85,12 @@ public sealed class RingBufferTests {
[TestCase(5)]
public void AddMultipleItemsOverflowingCapacityAndEnumerateFewer(int capacity) {
var buffer = PrepareRingBuffer(capacity, "a", "b", "c", "d", "e", "f");
Assert.That(buffer.EnumerateLast(2), Is.EquivalentTo(new [] { "e", "f" }));
Assert.That(buffer.EnumerateLast(2), Is.EquivalentTo(new[] { "e", "f" }));
}
[TestCase(3, ExpectedResult = new [] { "d", "e", "f" })]
[TestCase(4, ExpectedResult = new [] { "c", "d", "e", "f" })]
[TestCase(5, ExpectedResult = new [] { "b", "c", "d", "e", "f" })]
[TestCase(3, ExpectedResult = new[] { "d", "e", "f" })]
[TestCase(4, ExpectedResult = new[] { "c", "d", "e", "f" })]
[TestCase(5, ExpectedResult = new[] { "b", "c", "d", "e", "f" })]
public string[] AddMultipleItemsOverflowingCapacityAndEnumerateMaxValue(int capacity) {
var buffer = PrepareRingBuffer(capacity, "a", "b", "c", "d", "e", "f");
return buffer.EnumerateLast(uint.MaxValue).ToArray();

View File

@@ -9,12 +9,12 @@ public abstract class FormCustomValidationAttribute<TModel, TValue> : Validation
protected sealed override ValidationResult? IsValid(object? value, ValidationContext validationContext) {
if (value is not TValue typedValue) {
return new ValidationResult(null, new [] { FieldName });
return new ValidationResult(null, new[] { FieldName });
}
var model = (TModel) validationContext.ObjectInstance;
var result = Validate(model, typedValue);
return result == ValidationResult.Success ? result : new ValidationResult(result?.ErrorMessage, new [] { FieldName });
return result == ValidationResult.Success ? result : new ValidationResult(result?.ErrorMessage, new[] { FieldName });
}
protected abstract string FieldName { get; }

View File

@@ -9,7 +9,7 @@ public abstract class FormValidationAttribute<TModel, TValue> : ValidationAttrib
protected sealed override ValidationResult? IsValid(object? value, ValidationContext validationContext) {
var model = (TModel) validationContext.ObjectInstance;
return value is TValue typedValue && IsValid(model, typedValue) ? ValidationResult.Success : new ValidationResult(null, new [] { FieldName });
return value is TValue typedValue && IsValid(model, typedValue) ? ValidationResult.Success : new ValidationResult(null, new[] { FieldName });
}
protected abstract string FieldName { get; }

View File

@@ -1,6 +1,6 @@
@using Phantom.Web.Services.Authentication
@using Phantom.Common.Data.Web.Users
@using Phantom.Web.Services
@using Phantom.Common.Data.Web.Users
@using Phantom.Web.Services.Authentication
@inject ApplicationProperties ApplicationProperties
<div class="navbar navbar-dark">

View File

@@ -3,8 +3,8 @@
@using System.Collections.Immutable
@using Phantom.Common.Data.Web.AuditLog
@using Phantom.Common.Data.Web.Users
@using Phantom.Web.Services.Users
@using Phantom.Web.Services.Instances
@using Phantom.Web.Services.Users
@inherits PhantomComponent
@inject AuditLogManager AuditLogManager
@inject InstanceManager InstanceManager

View File

@@ -6,7 +6,7 @@
@using Phantom.Web.Services.Agents
@using Phantom.Web.Services.Events
@using Phantom.Web.Services.Instances
@inherits Phantom.Web.Components.PhantomComponent
@inherits PhantomComponent
@inject AgentManager AgentManager
@inject EventLogManager EventLogManager
@inject InstanceManager InstanceManager

View File

@@ -1,10 +1,10 @@
@page "/"
@inherits Phantom.Web.Components.PhantomComponent
@inherits PhantomComponent
<h1>Home</h1>
@if (username != null) {
<p>Welcome back, @username!</p>
<p>Welcome back, @username!</p>
}
@code {

View File

@@ -43,10 +43,10 @@
</Cell>
<Cell class="text-center">
@if (instance.PlayerCounts is var (online, maximum)) {
<p class="font-monospace">@online.ToString() / @maximum.ToString()</p>
<p class="font-monospace">@online.ToString() / @maximum.ToString()</p>
}
else {
<p class="font-monospace">-</p>
<p class="font-monospace">-</p>
}
</Cell>
<Cell>@configuration.MinecraftServerKind @configuration.MinecraftVersion</Cell>

View File

@@ -1,7 +1,7 @@
@page "/login"
@using System.ComponentModel.DataAnnotations
@using Phantom.Web.Services
@using Phantom.Web.Services.Authentication
@using System.ComponentModel.DataAnnotations
@attribute [AllowAnonymous]
@inject Navigation Navigation
@inject UserLoginManager LoginManager

View File

@@ -1,4 +1,6 @@
@page "/setup"
@using System.ComponentModel.DataAnnotations
@using System.Security.Cryptography
@using Phantom.Common.Data
@using Phantom.Common.Data.Web.Users
@using Phantom.Common.Data.Web.Users.CreateOrUpdateAdministratorUserResults
@@ -7,8 +9,6 @@
@using Phantom.Web.Services
@using Phantom.Web.Services.Authentication
@using Phantom.Web.Services.Rpc
@using System.ComponentModel.DataAnnotations
@using System.Security.Cryptography
@attribute [AllowAnonymous]
@inject ApplicationProperties ApplicationProperties
@inject UserLoginManager LoginManager
@@ -44,7 +44,7 @@
@code {
private readonly CreateAdministratorAccountFormModel form = new();
private readonly CreateAdministratorAccountFormModel form = new ();
private sealed class CreateAdministratorAccountFormModel : FormModel {
[Required]
@@ -91,12 +91,12 @@
private async Task<Result<string>> CreateOrUpdateAdministrator() {
var reply = await ControllerConnection.Send<CreateOrUpdateAdministratorUserMessage, CreateOrUpdateAdministratorUserResult>(new CreateOrUpdateAdministratorUserMessage(form.Username, form.Password), Timeout.InfiniteTimeSpan);
return reply switch {
Success => Result.Ok,
CreationFailed fail => fail.Error.ToSentences("\n"),
UpdatingFailed fail => fail.Error.ToSentences("\n"),
AddingToRoleFailed => "Could not assign administrator role to user.",
null => "Timed out.",
_ => "Unknown error."
Success => Result.Ok,
CreationFailed fail => fail.Error.ToSentences("\n"),
UpdatingFailed fail => fail.Error.ToSentences("\n"),
AddingToRoleFailed => "Could not assign administrator role to user.",
null => "Timed out.",
_ => "Unknown error."
};
}

View File

@@ -1,11 +1,11 @@
@page "/users"
@attribute [Authorize(Permission.ViewUsersPolicy)]
@using System.Collections.Immutable
@using Phantom.Common.Data.Web.Users
@using Phantom.Web.Services.Authentication
@using Phantom.Web.Services.Authorization
@using Phantom.Web.Services.Users
@using Phantom.Common.Data.Web.Users
@inherits Phantom.Web.Components.PhantomComponent
@inherits PhantomComponent
@inject UserManager UserManager
@inject RoleManager RoleManager
@inject UserRoleManager UserRoleManager

View File

@@ -1,56 +1,56 @@
@using Phantom.Common.Data.Instance
<nobr>
@switch (Status) {
case InstanceIsOffline:
<span class="fw-semibold">Offline</span>
break;
@switch (Status) {
case InstanceIsOffline:
<span class="fw-semibold">Offline</span>
break;
case InstanceIsInvalid invalid:
<span class="fw-semibold text-danger">Invalid <sup title="@invalid.Reason">[?]</sup></span>
break;
case InstanceIsInvalid invalid:
<span class="fw-semibold text-danger">Invalid <sup title="@invalid.Reason">[?]</sup></span>
break;
case InstanceIsNotRunning:
<span class="fw-semibold">Not Running</span>
break;
case InstanceIsNotRunning:
<span class="fw-semibold">Not Running</span>
break;
case InstanceIsDownloading downloading:
<ProgressBar Value="@downloading.Progress" Maximum="100">
<span class="fw-semibold">Downloading Server</span> (@downloading.Progress%)
</ProgressBar>
break;
case InstanceIsDownloading downloading:
<ProgressBar Value="@downloading.Progress" Maximum="100">
<span class="fw-semibold">Downloading Server</span> (@downloading.Progress%)
</ProgressBar>
break;
case InstanceIsLaunching:
<div class="spinner-border" role="status"></div>
<span class="fw-semibold">&nbsp;Launching</span>
break;
case InstanceIsLaunching:
<div class="spinner-border" role="status"></div>
<span class="fw-semibold">&nbsp;Launching</span>
break;
case InstanceIsRunning:
<span class="fw-semibold text-success">Running</span>
break;
case InstanceIsRunning:
<span class="fw-semibold text-success">Running</span>
break;
case InstanceIsBackingUp:
<div class="spinner-border" role="status"></div>
<span class="fw-semibold">&nbsp;Backing Up</span>
break;
case InstanceIsBackingUp:
<div class="spinner-border" role="status"></div>
<span class="fw-semibold">&nbsp;Backing Up</span>
break;
case InstanceIsRestarting:
<div class="spinner-border" role="status"></div>
<span class="fw-semibold">&nbsp;Restarting</span>
break;
case InstanceIsRestarting:
<div class="spinner-border" role="status"></div>
<span class="fw-semibold">&nbsp;Restarting</span>
break;
case InstanceIsStopping:
<div class="spinner-border" role="status"></div>
<span class="fw-semibold">&nbsp;Stopping</span>
break;
case InstanceIsStopping:
<div class="spinner-border" role="status"></div>
<span class="fw-semibold">&nbsp;Stopping</span>
break;
case InstanceIsFailed failed:
<span class="fw-semibold text-danger">Failed <sup title="@failed.Reason.ToSentence()">[?]</sup></span>
break;
case InstanceIsFailed failed:
<span class="fw-semibold text-danger">Failed <sup title="@failed.Reason.ToSentence()">[?]</sup></span>
break;
default:
<span class="fw-semibold">Unknown</span>
break;
}
default:
<span class="fw-semibold">Unknown</span>
break;
}
</nobr>
@code {

View File

@@ -27,7 +27,7 @@
@code {
private ImmutableDictionary<Guid, RoleInfo> allRolesByGuid = ImmutableDictionary<Guid, RoleInfo>.Empty;
private List<RoleItem> items = new();
private List<RoleItem> items = new ();
protected override async Task BeforeShown(UserInfo user) {
var allRoles = await RoleManager.GetAll(CancellationToken);
@@ -77,11 +77,11 @@
var errors = new List<string>();
foreach (var roleGuid in failedToAdd) {
errors.Add("Could not add role: " + GetRoleName(roleGuid));
errors.Add("Could not add role: " + GetRoleName(roleGuid));
}
foreach (var roleGuid in failedToRemove) {
errors.Add("Could not remove role: " + GetRoleName(roleGuid));
errors.Add("Could not remove role: " + GetRoleName(roleGuid));
}
OnEditFailure(string.Join("\n", errors));

View File

@@ -2,22 +2,27 @@
using Phantom.Common.Data.Replies;
using Phantom.Common.Data.Web.Minecraft;
using Phantom.Common.Data.Web.Users;
using Phantom.Common.Data.Web.Users.AddUserErrors;
using Phantom.Common.Data.Web.Users.PasswordRequirementViolations;
using Phantom.Common.Data.Web.Users.SetUserPasswordErrors;
using Phantom.Common.Data.Web.Users.UsernameRequirementViolations;
using PasswordIsInvalid = Phantom.Common.Data.Web.Users.AddUserErrors.PasswordIsInvalid;
namespace Phantom.Web.Utils;
static class Messages {
public static string ToSentences(this AddUserError error, string delimiter) {
return error switch {
Common.Data.Web.Users.AddUserErrors.NameIsInvalid e => e.Violation.ToSentence(),
Common.Data.Web.Users.AddUserErrors.PasswordIsInvalid e => string.Join(delimiter, e.Violations.Select(static v => v.ToSentence())),
Common.Data.Web.Users.AddUserErrors.NameAlreadyExists => "Username is already occupied.",
_ => "Unknown error."
NameIsInvalid e => e.Violation.ToSentence(),
PasswordIsInvalid e => string.Join(delimiter, e.Violations.Select(static v => v.ToSentence())),
NameAlreadyExists => "Username is already occupied.",
_ => "Unknown error."
};
}
public static string ToSentences(this SetUserPasswordError error, string delimiter) {
return error switch {
Common.Data.Web.Users.SetUserPasswordErrors.UserNotFound => "User not found.",
UserNotFound => "User not found.",
Common.Data.Web.Users.SetUserPasswordErrors.PasswordIsInvalid e => string.Join(delimiter, e.Violations.Select(static v => v.ToSentence())),
_ => "Unknown error."
};
@@ -25,19 +30,19 @@ static class Messages {
public static string ToSentence(this UsernameRequirementViolation violation) {
return violation switch {
Common.Data.Web.Users.UsernameRequirementViolations.IsEmpty => "Username must not be empty.",
Common.Data.Web.Users.UsernameRequirementViolations.TooLong v => "Username must not be longer than " + v.MaxLength + " character(s).",
_ => "Unknown error."
IsEmpty => "Username must not be empty.",
TooLong v => "Username must not be longer than " + v.MaxLength + " character(s).",
_ => "Unknown error."
};
}
public static string ToSentence(this PasswordRequirementViolation violation) {
return violation switch {
Common.Data.Web.Users.PasswordRequirementViolations.TooShort v => "Password must be at least " + v.MinimumLength + " character(s) long.",
Common.Data.Web.Users.PasswordRequirementViolations.MustContainLowercaseLetter => "Password must contain a lowercase letter.",
Common.Data.Web.Users.PasswordRequirementViolations.MustContainUppercaseLetter => "Password must contain an uppercase letter.",
Common.Data.Web.Users.PasswordRequirementViolations.MustContainDigit => "Password must contain a digit.",
_ => "Unknown error."
TooShort v => "Password must be at least " + v.MinimumLength + " character(s) long.",
MustContainLowercaseLetter => "Password must contain a lowercase letter.",
MustContainUppercaseLetter => "Password must contain an uppercase letter.",
MustContainDigit => "Password must contain a digit.",
_ => "Unknown error."
};
}

View File

@@ -1,6 +1,6 @@
{
"sdk": {
"version": "8.0.0",
"version": "9.0.0",
"rollForward": "latestMinor",
"allowPrerelease": true
}