mirror of
https://github.com/chylex/Minecraft-Phantom-Panel.git
synced 2024-11-25 16:42:54 +01:00
Compare commits
3 Commits
2471dc04f1
...
1ded0e50b2
Author | SHA1 | Date | |
---|---|---|---|
1ded0e50b2 | |||
e679b17e3b | |||
578ec2d11c |
@ -4,7 +4,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Phantom.Utils.Collections;
|
||||
|
||||
public sealed class Table<TRow, TKey> : IReadOnlyList<TRow>, IReadOnlyDictionary<TKey, TRow> where TRow : notnull where TKey : notnull {
|
||||
public sealed class TableData<TRow, TKey> : IReadOnlyList<TRow>, IReadOnlyDictionary<TKey, TRow> where TRow : notnull where TKey : notnull {
|
||||
private readonly List<TRow> rowList = new();
|
||||
private readonly Dictionary<TKey, TRow> rowDictionary = new ();
|
||||
|
@ -4,6 +4,6 @@ namespace Phantom.Utils.Runtime;
|
||||
|
||||
public static class AssemblyAttributes {
|
||||
public static string GetFullVersion(Assembly assembly) {
|
||||
return assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion.Replace('+', '/') ?? string.Empty;
|
||||
return assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion.Replace('+', '-') ?? string.Empty;
|
||||
}
|
||||
}
|
||||
|
6
Web/Phantom.Web.Bootstrap/src/bootstrap.scss
vendored
6
Web/Phantom.Web.Bootstrap/src/bootstrap.scss
vendored
@ -39,8 +39,10 @@ $dropdown-link-active-bg: mix($gray-200, $gray-300, 75%);
|
||||
|
||||
@import "./components";
|
||||
|
||||
.spinner-border-sm {
|
||||
--bs-spinner-border-width: 0.15em;
|
||||
.spinner-border {
|
||||
--bs-spinner-width: 1em;
|
||||
--bs-spinner-height: 1em;
|
||||
--bs-spinner-border-width: 0.15rem;
|
||||
}
|
||||
|
||||
.progress {
|
||||
|
@ -1,9 +1,9 @@
|
||||
.progress {
|
||||
height: 4px;
|
||||
margin: 0.15rem 0;
|
||||
}
|
||||
|
||||
.progress-label {
|
||||
width: 100%;
|
||||
margin-bottom: 0.15rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
14
Web/Phantom.Web.Components/Graphics/TimeWithOffset.razor
Normal file
14
Web/Phantom.Web.Components/Graphics/TimeWithOffset.razor
Normal file
@ -0,0 +1,14 @@
|
||||
@using System.Globalization
|
||||
<p>
|
||||
<time datetime="@Time.ToString("o", CultureInfo.InvariantCulture)" data-time-type="relative">
|
||||
@Time.ToString("dd MMM yyyy, HH:mm:ss", CultureInfo.InvariantCulture)
|
||||
</time>
|
||||
</p>
|
||||
<small>@Time.ToString("zzz", CultureInfo.InvariantCulture)</small>
|
||||
|
||||
@code {
|
||||
|
||||
[Parameter, EditorRequired]
|
||||
public DateTimeOffset Time { get; set; }
|
||||
|
||||
}
|
@ -13,4 +13,8 @@
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.Web" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Phantom.Web.Services\Phantom.Web.Services.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -6,7 +6,7 @@ using Phantom.Web.Services.Authorization;
|
||||
using ILogger = Serilog.ILogger;
|
||||
using UserInfo = Phantom.Web.Services.Authentication.UserInfo;
|
||||
|
||||
namespace Phantom.Web.Base;
|
||||
namespace Phantom.Web.Components;
|
||||
|
||||
public abstract class PhantomComponent : ComponentBase, IDisposable {
|
||||
private static readonly ILogger Logger = PhantomLogger.Create<PhantomComponent>();
|
@ -1,4 +1,4 @@
|
||||
<th style="min-width: @minWidth; width: @preferredWidth;" class="@Class">
|
||||
<th style="@style" class="@Class">
|
||||
@ChildContent
|
||||
</th>
|
||||
|
||||
@ -7,32 +7,29 @@
|
||||
[Parameter]
|
||||
public string Class { get; set; } = string.Empty;
|
||||
|
||||
[Parameter]
|
||||
public string? MinWidth { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string? Width { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public RenderFragment? ChildContent { get; set; }
|
||||
|
||||
private string minWidth = string.Empty;
|
||||
private string preferredWidth = string.Empty;
|
||||
private string style = string.Empty;
|
||||
|
||||
protected override void OnParametersSet() {
|
||||
if (string.IsNullOrEmpty(Width)) {
|
||||
minWidth = string.Empty;
|
||||
preferredWidth = string.Empty;
|
||||
return;
|
||||
List<string> styles = new (2);
|
||||
|
||||
if (MinWidth != null) {
|
||||
styles.Add("min-width: " + MinWidth);
|
||||
}
|
||||
|
||||
if (Width != null) {
|
||||
styles.Add("width: " + Width);
|
||||
}
|
||||
|
||||
int separator = Width.IndexOf(';');
|
||||
if (separator == -1) {
|
||||
minWidth = Width;
|
||||
preferredWidth = Width;
|
||||
return;
|
||||
}
|
||||
|
||||
var span = Width.AsSpan();
|
||||
minWidth = span[..separator].Trim().ToString();
|
||||
preferredWidth = span[(separator + 1)..].Trim().ToString();
|
||||
style = string.Join(';', styles);
|
||||
}
|
||||
|
||||
}
|
||||
|
58
Web/Phantom.Web.Components/Tables/Table.razor
Normal file
58
Web/Phantom.Web.Components/Tables/Table.razor
Normal file
@ -0,0 +1,58 @@
|
||||
@typeparam TItem
|
||||
|
||||
<div class="horizontal-scroll">
|
||||
<table class="table align-middle@(Class.Length == 0 ? "" : " " + Class)">
|
||||
|
||||
<thead>
|
||||
<tr>
|
||||
@HeaderRow
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
@if (Items is null) {
|
||||
<tbody>
|
||||
<tr>
|
||||
<td colspan="1000" class="fw-semibold">
|
||||
Loading...
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
}
|
||||
else if (Items.Count > 0) {
|
||||
<tbody>
|
||||
@foreach (var item in Items) {
|
||||
<tr>
|
||||
@ItemRow(item)
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
}
|
||||
else if (NoItemsRow != null) {
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="1000">@NoItemsRow</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
}
|
||||
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
|
||||
[Parameter]
|
||||
public string Class { get; set; } = string.Empty;
|
||||
|
||||
[Parameter, EditorRequired]
|
||||
public RenderFragment HeaderRow { get; set; } = null!;
|
||||
|
||||
[Parameter, EditorRequired]
|
||||
public RenderFragment<TItem> ItemRow { get; set; } = null!;
|
||||
|
||||
[Parameter]
|
||||
public RenderFragment? NoItemsRow { get; set; } = null!;
|
||||
|
||||
[Parameter, EditorRequired]
|
||||
public IReadOnlyList<TItem>? Items { get; set; }
|
||||
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
@using Phantom.Web.Components
|
||||
@using Phantom.Web.Components.Forms
|
||||
@using Phantom.Web.Components.Forms.Base
|
||||
@using Phantom.Web.Components.Forms.Fields
|
||||
|
6
Web/Phantom.Web.Services/ApplicationProperties.cs
Normal file
6
Web/Phantom.Web.Services/ApplicationProperties.cs
Normal file
@ -0,0 +1,6 @@
|
||||
namespace Phantom.Web.Services;
|
||||
|
||||
public sealed record ApplicationProperties(
|
||||
string Version,
|
||||
byte[] AdministratorToken
|
||||
);
|
@ -9,13 +9,13 @@ namespace Phantom.Web.Services.Authentication;
|
||||
public sealed class UserLoginManager {
|
||||
private static readonly ILogger Logger = PhantomLogger.Create<UserLoginManager>();
|
||||
|
||||
private readonly INavigation navigation;
|
||||
private readonly Navigation navigation;
|
||||
private readonly UserSessionManager sessionManager;
|
||||
private readonly UserSessionBrowserStorage sessionBrowserStorage;
|
||||
private readonly CustomAuthenticationStateProvider authenticationStateProvider;
|
||||
private readonly ControllerConnection controllerConnection;
|
||||
|
||||
public UserLoginManager(INavigation navigation, UserSessionManager sessionManager, UserSessionBrowserStorage sessionBrowserStorage, CustomAuthenticationStateProvider authenticationStateProvider, ControllerConnection controllerConnection) {
|
||||
public UserLoginManager(Navigation navigation, UserSessionManager sessionManager, UserSessionBrowserStorage sessionBrowserStorage, CustomAuthenticationStateProvider authenticationStateProvider, ControllerConnection controllerConnection) {
|
||||
this.navigation = navigation;
|
||||
this.sessionManager = sessionManager;
|
||||
this.sessionBrowserStorage = sessionBrowserStorage;
|
||||
|
@ -1,9 +0,0 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Phantom.Web.Services;
|
||||
|
||||
public interface INavigation {
|
||||
string BasePath { get; }
|
||||
bool GetQueryParameter(string key, [MaybeNullWhen(false)] out string value);
|
||||
Task NavigateTo(string url, bool forceLoad = false);
|
||||
}
|
@ -2,11 +2,10 @@
|
||||
using System.Web;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Routing;
|
||||
using Phantom.Web.Services;
|
||||
|
||||
namespace Phantom.Web.Base;
|
||||
namespace Phantom.Web.Services;
|
||||
|
||||
sealed class Navigation : INavigation {
|
||||
public sealed class Navigation {
|
||||
public static Func<IServiceProvider, Navigation> Create(string basePath) {
|
||||
return provider => new Navigation(basePath, provider.GetRequiredService<NavigationManager>());
|
||||
}
|
||||
@ -27,6 +26,10 @@ sealed class Navigation : INavigation {
|
||||
value = query.Get(key);
|
||||
return value != null;
|
||||
}
|
||||
|
||||
public string CreateReturnUrl() {
|
||||
return navigationManager.ToBaseRelativePath(navigationManager.Uri).TrimEnd('/');
|
||||
}
|
||||
|
||||
public async Task NavigateTo(string url, bool forceLoad = false) {
|
||||
var newPath = BasePath + url;
|
@ -1,6 +1,5 @@
|
||||
@using Phantom.Web.Services
|
||||
@inject INavigation Nav
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject Navigation Navigation
|
||||
|
||||
<CascadingAuthenticationState>
|
||||
<Router AppAssembly="@typeof(App).Assembly">
|
||||
@ -12,8 +11,7 @@
|
||||
<p role="alert">You do not have permission to visit this page.</p>
|
||||
}
|
||||
else {
|
||||
var returnUrl = NavigationManager.ToBaseRelativePath(NavigationManager.Uri).TrimEnd('/');
|
||||
Nav.NavigateTo("login" + QueryString.Create("return", returnUrl), forceLoad: true);
|
||||
Navigation.NavigateTo("login" + QueryString.Create("return", Navigation.CreateReturnUrl()), forceLoad: true);
|
||||
}
|
||||
</NotAuthorized>
|
||||
</AuthorizeRouteView>
|
||||
|
@ -1,7 +0,0 @@
|
||||
using ILogger = Serilog.ILogger;
|
||||
|
||||
namespace Phantom.Web;
|
||||
|
||||
sealed record Configuration(ILogger Logger, string Host, ushort Port, string BasePath, string DataProtectionKeyFolderPath, CancellationToken CancellationToken) {
|
||||
public string HttpUrl => "http://" + Host + ":" + Port;
|
||||
}
|
@ -1,11 +1,15 @@
|
||||
@using Phantom.Web.Services.Authorization
|
||||
@using Phantom.Web.Services
|
||||
@using Phantom.Web.Services.Authorization
|
||||
@using Phantom.Common.Data.Web.Users
|
||||
@inject ServiceConfiguration Configuration
|
||||
@inject ApplicationProperties ApplicationProperties
|
||||
@inject PermissionManager PermissionManager
|
||||
|
||||
<div class="navbar navbar-dark">
|
||||
<div class="container-fluid">
|
||||
<a class="navbar-brand" href="">Phantom Panel</a>
|
||||
<div class="pt-1 pb-2">
|
||||
<a class="navbar-brand" href="">Phantom Panel</a>
|
||||
<small class="navbar-text">Version @ApplicationProperties.Version</small>
|
||||
</div>
|
||||
<button title="Navigation menu" class="navbar-toggler" @onclick="ToggleNavMenu">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
@ -44,9 +48,6 @@
|
||||
</Authorized>
|
||||
</AuthorizeView>
|
||||
</nav>
|
||||
<footer>
|
||||
Build @Configuration.Version
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
|
@ -1,7 +1,7 @@
|
||||
@page
|
||||
@using Phantom.Web.Services
|
||||
@model Phantom.Web.Layout.ErrorModel
|
||||
@inject INavigation Navigation
|
||||
@inject Navigation Navigation
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
@ -1,7 +1,7 @@
|
||||
@using Phantom.Web.Services
|
||||
@namespace Phantom.Web.Layout
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
@inject INavigation Navigation
|
||||
@inject Navigation Navigation
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
@ -2,80 +2,68 @@
|
||||
@using Phantom.Common.Data.Web.Agent
|
||||
@using Phantom.Utils.Collections
|
||||
@using Phantom.Web.Services.Agents
|
||||
@inherits PhantomComponent
|
||||
@inherits Phantom.Web.Components.PhantomComponent
|
||||
@inject AgentManager AgentManager
|
||||
|
||||
<h1>Agents</h1>
|
||||
|
||||
<table class="table align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<Column Width="200px; 44%">Name</Column>
|
||||
<Column Width=" 90px; 19%" Class="text-end">Instances</Column>
|
||||
<Column Width="145px; 21%" Class="text-end">Memory</Column>
|
||||
<Column Width="180px; 8%">Version</Column>
|
||||
<Column Width="320px">Identifier</Column>
|
||||
<Column Width="100px; 8%" Class="text-center">Status</Column>
|
||||
<Column Width="215px" Class="text-end">Last Ping</Column>
|
||||
</tr>
|
||||
</thead>
|
||||
@if (!agentTable.IsEmpty) {
|
||||
<tbody>
|
||||
@foreach (var agent in agentTable) {
|
||||
var usedInstances = agent.Stats?.RunningInstanceCount;
|
||||
var usedMemory = agent.Stats?.RunningInstanceMemory.InMegabytes;
|
||||
|
||||
<tr>
|
||||
<td>@agent.Name</td>
|
||||
<td class="text-end">
|
||||
<ProgressBar Value="@(usedInstances ?? 0)" Maximum="@agent.MaxInstances">
|
||||
@(usedInstances?.ToString() ?? "?") / @agent.MaxInstances
|
||||
</ProgressBar>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<ProgressBar Value="@(usedMemory ?? 0)" Maximum="@agent.MaxMemory.InMegabytes">
|
||||
@(usedMemory?.ToString() ?? "?") / @agent.MaxMemory.InMegabytes MB
|
||||
</ProgressBar>
|
||||
</td>
|
||||
<td class="text-condensed">
|
||||
Build: <code>@agent.BuildVersion</code>
|
||||
<br>
|
||||
Protocol: <code>v@(agent.ProtocolVersion)</code>
|
||||
</td>
|
||||
<td>
|
||||
<code class="text-uppercase">@agent.Guid.ToString()</code>
|
||||
</td>
|
||||
@if (agent.IsOnline) {
|
||||
<td class="text-center text-success">Online</td>
|
||||
<td class="text-end"></td>
|
||||
}
|
||||
else {
|
||||
<td class="text-center text-danger">Offline</td>
|
||||
@if (agent.LastPing is {} lastPing) {
|
||||
<td class="text-end">
|
||||
<time datetime="@lastPing.ToString("o")" data-time-type="relative">@lastPing.ToString()</time>
|
||||
</td>
|
||||
}
|
||||
else {
|
||||
<td class="text-end">-</td>
|
||||
}
|
||||
}
|
||||
</tr>
|
||||
<Table Items="agentTable">
|
||||
<HeaderRow>
|
||||
<Column Width="50%">Name</Column>
|
||||
<Column Class="text-end" Width="24%" MinWidth="90px">Instances</Column>
|
||||
<Column Class="text-end" Width="26%" MinWidth="145px">Memory</Column>
|
||||
<Column>Version</Column>
|
||||
<Column Class="text-center">Status</Column>
|
||||
<Column Class="text-end" MinWidth="200px">Last Ping</Column>
|
||||
</HeaderRow>
|
||||
<ItemRow Context="agent">
|
||||
@{
|
||||
var usedInstances = agent.Stats?.RunningInstanceCount;
|
||||
var usedMemory = agent.Stats?.RunningInstanceMemory.InMegabytes;
|
||||
}
|
||||
<td>
|
||||
<p class="fw-semibold">@agent.Name</p>
|
||||
<small class="font-monospace text-uppercase">@agent.Guid.ToString()</small>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<ProgressBar Value="@(usedInstances ?? 0)" Maximum="@agent.MaxInstances">
|
||||
@(usedInstances?.ToString() ?? "?") / @agent.MaxInstances.ToString()
|
||||
</ProgressBar>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<ProgressBar Value="@(usedMemory ?? 0)" Maximum="@agent.MaxMemory.InMegabytes">
|
||||
@(usedMemory?.ToString() ?? "?") / @agent.MaxMemory.InMegabytes.ToString() MB
|
||||
</ProgressBar>
|
||||
</td>
|
||||
<td class="text-condensed">
|
||||
Build: <span class="font-monospace">@agent.BuildVersion</span>
|
||||
<br>
|
||||
Protocol: <span class="font-monospace">v@(agent.ProtocolVersion.ToString())</span>
|
||||
</td>
|
||||
@if (agent.IsOnline) {
|
||||
<td class="fw-semibold text-center text-success">Online</td>
|
||||
<td class="text-end">-</td>
|
||||
}
|
||||
else {
|
||||
<td class="fw-semibold text-center">Offline</td>
|
||||
<td class="text-end">
|
||||
@if (agent.LastPing is {} lastPing) {
|
||||
<TimeWithOffset Time="lastPing" />
|
||||
}
|
||||
</tbody>
|
||||
}
|
||||
else {
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="7">No agents registered.</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
}
|
||||
</table>
|
||||
else {
|
||||
<text>N/A</text>
|
||||
}
|
||||
</td>
|
||||
}
|
||||
</ItemRow>
|
||||
<NoItemsRow>
|
||||
No agents registered.
|
||||
</NoItemsRow>
|
||||
</Table>
|
||||
|
||||
@code {
|
||||
|
||||
private readonly Table<AgentWithStats, Guid> agentTable = new();
|
||||
private readonly TableData<AgentWithStats, Guid> agentTable = new();
|
||||
|
||||
protected override void OnInitialized() {
|
||||
AgentManager.AgentsChanged.Subscribe(this, agents => {
|
||||
|
@ -5,55 +5,49 @@
|
||||
@using Phantom.Common.Data.Web.Users
|
||||
@using Phantom.Web.Services.Users
|
||||
@using Phantom.Web.Services.Instances
|
||||
@inherits PhantomComponent
|
||||
@inherits Phantom.Web.Components.PhantomComponent
|
||||
@inject AuditLogManager AuditLogManager
|
||||
@inject InstanceManager InstanceManager
|
||||
@inject UserManager UserManager
|
||||
|
||||
<h1>Audit Log</h1>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<Column Width="165px" Class="text-end">Time</Column>
|
||||
<Column Width="320px; 20%">User</Column>
|
||||
<Column Width="160px">Event Type</Column>
|
||||
<Column Width="320px; 20%">Subject</Column>
|
||||
<Column Width="100px; 60%">Data</Column>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var logItem in logItems) {
|
||||
DateTimeOffset time = logItem.UtcTime.ToLocalTime();
|
||||
<tr>
|
||||
<td class="text-end">
|
||||
<time datetime="@time.ToString("o")">@time.ToString()</time>
|
||||
</td>
|
||||
<td>
|
||||
@(logItem.UserName ?? "-")
|
||||
<br>
|
||||
<code class="text-uppercase">@logItem.UserGuid</code>
|
||||
</td>
|
||||
<td>@logItem.EventType.ToNiceString()</td>
|
||||
<td>
|
||||
@if (logItem.SubjectId is {} subjectId && GetSubjectName(logItem.SubjectType, subjectId) is {} subjectName) {
|
||||
@subjectName
|
||||
<br>
|
||||
}
|
||||
<code class="text-uppercase">@(logItem.SubjectId ?? "-")</code>
|
||||
</td>
|
||||
<td>
|
||||
<code>@logItem.JsonData</code>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
<Table TItem="AuditLogItem" Items="logItems">
|
||||
<HeaderRow>
|
||||
<Column Class="text-end" MinWidth="200px">Time</Column>
|
||||
<Column>User</Column>
|
||||
<Column>Event Type</Column>
|
||||
<Column>Subject</Column>
|
||||
<Column Width="100%">Data</Column>
|
||||
</HeaderRow>
|
||||
<ItemRow Context="logItem">
|
||||
<td class="text-end">
|
||||
<TimeWithOffset Time="logItem.UtcTime.ToLocalTime()" />
|
||||
</td>
|
||||
<td>
|
||||
<p class="fw-semibold">@(logItem.UserName ?? "-")</p>
|
||||
<small class="font-monospace text-uppercase">@logItem.UserGuid.ToString()</small>
|
||||
</td>
|
||||
<td>
|
||||
<p>@logItem.EventType.ToNiceString()</p>
|
||||
</td>
|
||||
<td>
|
||||
<p class="fw-semibold">@(logItem.SubjectId is {} subjectId && GetSubjectName(logItem.SubjectType, subjectId) is {} subjectName ? subjectName : "-")</p>
|
||||
<small class="font-monospace text-uppercase">@(logItem.SubjectId ?? "-")</small>
|
||||
</td>
|
||||
<td>
|
||||
<code>@logItem.JsonData</code>
|
||||
</td>
|
||||
</ItemRow>
|
||||
<NoItemsRow>
|
||||
No audit log entries found.
|
||||
</NoItemsRow>
|
||||
</Table>
|
||||
|
||||
@code {
|
||||
|
||||
private CancellationTokenSource? initializationCancellationTokenSource;
|
||||
private ImmutableArray<AuditLogItem> logItems = ImmutableArray<AuditLogItem>.Empty;
|
||||
private ImmutableArray<AuditLogItem>? logItems;
|
||||
private ImmutableDictionary<Guid, string>? userNamesByGuid;
|
||||
private ImmutableDictionary<Guid, string> instanceNamesByGuid = ImmutableDictionary<Guid, string>.Empty;
|
||||
|
||||
@ -72,9 +66,9 @@
|
||||
|
||||
private string? GetSubjectName(AuditLogSubjectType type, string id) {
|
||||
return type switch {
|
||||
AuditLogSubjectType.Instance => instanceNamesByGuid.TryGetValue(Guid.Parse(id), out var name) ? name : null,
|
||||
AuditLogSubjectType.User => userNamesByGuid != null && userNamesByGuid.TryGetValue(Guid.Parse(id), out var name) ? name : null,
|
||||
_ => null
|
||||
AuditLogSubjectType.Instance => instanceNamesByGuid.TryGetValue(Guid.Parse(id), out var name) ? name : null,
|
||||
AuditLogSubjectType.User => userNamesByGuid != null && userNamesByGuid.TryGetValue(Guid.Parse(id), out var name) ? name : null,
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -6,60 +6,52 @@
|
||||
@using Phantom.Web.Services.Agents
|
||||
@using Phantom.Web.Services.Events
|
||||
@using Phantom.Web.Services.Instances
|
||||
@inherits PhantomComponent
|
||||
@inherits Phantom.Web.Components.PhantomComponent
|
||||
@inject AgentManager AgentManager
|
||||
@inject EventLogManager EventLogManager
|
||||
@inject InstanceManager InstanceManager
|
||||
|
||||
<h1>Event Log</h1>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<Column Width="165px" Class="text-end">Time</Column>
|
||||
<Column Width="320px; 20%">Agent</Column>
|
||||
<Column Width="160px">Event Type</Column>
|
||||
<Column Width="320px; 20%">Subject</Column>
|
||||
<Column Width="100px; 60%">Data</Column>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var logItem in logItems) {
|
||||
DateTimeOffset time = logItem.UtcTime.ToLocalTime();
|
||||
<tr>
|
||||
<td class="text-end">
|
||||
<time datetime="@time.ToString("o")">@time.ToString()</time>
|
||||
</td>
|
||||
<td>
|
||||
@if (logItem.AgentGuid is {} agentGuid) {
|
||||
@(GetAgentName(agentGuid))
|
||||
<br>
|
||||
<code class="text-uppercase">@agentGuid</code>
|
||||
}
|
||||
else {
|
||||
<text>-</text>
|
||||
}
|
||||
</td>
|
||||
<td>@logItem.EventType.ToNiceString()</td>
|
||||
<td>
|
||||
@if (GetSubjectName(logItem.SubjectType, logItem.SubjectId) is {} subjectName) {
|
||||
@subjectName
|
||||
<br>
|
||||
}
|
||||
<code class="text-uppercase">@logItem.SubjectId</code>
|
||||
</td>
|
||||
<td>
|
||||
<code>@logItem.JsonData</code>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
<Table TItem="EventLogItem" Items="logItems">
|
||||
<HeaderRow>
|
||||
<Column Class="text-end" MinWidth="200px">Time</Column>
|
||||
<Column>Agent</Column>
|
||||
<Column>Event Type</Column>
|
||||
<Column>Subject</Column>
|
||||
<Column Width="100%">Data</Column>
|
||||
</HeaderRow>
|
||||
<ItemRow Context="logItem">
|
||||
<td class="text-end">
|
||||
<TimeWithOffset Time="logItem.UtcTime.ToLocalTime()" />
|
||||
</td>
|
||||
<td>
|
||||
@if (logItem.AgentGuid is {} agentGuid) {
|
||||
<p class="fw-semibold">@(GetAgentName(agentGuid))</p>
|
||||
<small class="font-monospace text-uppercase">@agentGuid.ToString()</small>
|
||||
}
|
||||
else {
|
||||
<text>-</text>
|
||||
}
|
||||
</td>
|
||||
<td>@logItem.EventType.ToNiceString()</td>
|
||||
<td>
|
||||
<p class="fw-semibold">@(GetSubjectName(logItem.SubjectType, logItem.SubjectId) ?? "-")</p>
|
||||
<small class="font-monospace text-uppercase">@(logItem.SubjectId)</small>
|
||||
</td>
|
||||
<td>
|
||||
<code>@logItem.JsonData</code>
|
||||
</td>
|
||||
</ItemRow>
|
||||
<NoItemsRow>
|
||||
No event log entries found.
|
||||
</NoItemsRow>
|
||||
</Table>
|
||||
|
||||
@code {
|
||||
|
||||
private CancellationTokenSource? initializationCancellationTokenSource;
|
||||
private ImmutableArray<EventLogItem> logItems = ImmutableArray<EventLogItem>.Empty;
|
||||
private ImmutableArray<EventLogItem>? logItems;
|
||||
private ImmutableDictionary<Guid, string> agentNamesByGuid = ImmutableDictionary<Guid, string>.Empty;
|
||||
private ImmutableDictionary<Guid, string> instanceNamesByGuid = ImmutableDictionary<Guid, string>.Empty;
|
||||
|
||||
@ -82,8 +74,8 @@
|
||||
|
||||
private string? GetSubjectName(EventLogSubjectType type, string id) {
|
||||
return type switch {
|
||||
EventLogSubjectType.Instance => instanceNamesByGuid.TryGetValue(Guid.Parse(id), out var name) ? name : null,
|
||||
_ => null
|
||||
EventLogSubjectType.Instance => instanceNamesByGuid.TryGetValue(Guid.Parse(id), out var name) ? name : null,
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@
|
||||
@using Phantom.Common.Data.Web.Users
|
||||
@using Phantom.Web.Services.Instances
|
||||
@using Phantom.Web.Services.Authorization
|
||||
@inherits PhantomComponent
|
||||
@inherits Phantom.Web.Components.PhantomComponent
|
||||
@inject InstanceManager InstanceManager
|
||||
|
||||
@if (Instance == null) {
|
||||
@ -14,14 +14,19 @@
|
||||
<p>Return to <a href="instances">all instances</a>.</p>
|
||||
}
|
||||
else {
|
||||
<h1>Instance: @Instance.Configuration.InstanceName</h1>
|
||||
<div class="d-flex flex-row align-items-center gap-3 mb-3">
|
||||
<h1 class="mb-0">Instance: @Instance.Configuration.InstanceName</h1>
|
||||
<span class="fs-4 text-muted">//</span>
|
||||
<div class="mt-2">
|
||||
<InstanceStatusText Status="Instance.Status" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex flex-row align-items-center gap-2">
|
||||
<PermissionView Permission="Permission.ControlInstances">
|
||||
<button type="button" class="btn btn-success" @onclick="LaunchInstance" disabled="@(isLaunchingInstance || !Instance.Status.CanLaunch())">Launch</button>
|
||||
<button type="button" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#stop-instance" disabled="@(!Instance.Status.CanStop())">Stop...</button>
|
||||
<span><!-- extra spacing --></span>
|
||||
</PermissionView>
|
||||
<InstanceStatusText Status="Instance.Status" />
|
||||
<PermissionView Permission="Permission.CreateInstances">
|
||||
<a href="instances/@InstanceGuid/edit" class="btn btn-warning ms-auto">Edit Configuration</a>
|
||||
</PermissionView>
|
||||
|
@ -3,7 +3,7 @@
|
||||
@using Phantom.Common.Data.Instance
|
||||
@using Phantom.Common.Data.Web.Users
|
||||
@using Phantom.Web.Services.Instances
|
||||
@inherits PhantomComponent
|
||||
@inherits Phantom.Web.Components.PhantomComponent
|
||||
@inject InstanceManager InstanceManager
|
||||
|
||||
@if (InstanceConfiguration == null) {
|
||||
|
@ -16,66 +16,56 @@
|
||||
<a href="instances/create" class="btn btn-primary" role="button">New Instance</a>
|
||||
</PermissionView>
|
||||
|
||||
<table class="table align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<Column Width="200px; 28%">Agent</Column>
|
||||
<Column Width="200px; 28%">Name</Column>
|
||||
<Column Width="130px; 11%">Version</Column>
|
||||
<Column Width="110px; 8%" Class="text-center">Server Port</Column>
|
||||
<Column Width="110px; 8%" Class="text-center">Rcon Port</Column>
|
||||
<Column Width=" 90px; 8%" Class="text-end">Memory</Column>
|
||||
<Column Width="320px">Identifier</Column>
|
||||
<Column Width="200px; 9%">Status</Column>
|
||||
<Column Width=" 75px">Actions</Column>
|
||||
</tr>
|
||||
</thead>
|
||||
@if (!instances.IsEmpty) {
|
||||
<tbody>
|
||||
@foreach (var (configuration, status, _) in instances) {
|
||||
var agentName = agentNamesByGuid.TryGetValue(configuration.AgentGuid, out var name) ? name : string.Empty;
|
||||
var instanceGuid = configuration.InstanceGuid.ToString();
|
||||
<tr>
|
||||
<td>@agentName</td>
|
||||
<td>@configuration.InstanceName</td>
|
||||
<td>@configuration.MinecraftServerKind @configuration.MinecraftVersion</td>
|
||||
<td class="text-center">
|
||||
<code>@configuration.ServerPort</code>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<code>@configuration.RconPort</code>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<code>@configuration.MemoryAllocation.InMegabytes MB</code>
|
||||
</td>
|
||||
<td>
|
||||
<code class="text-uppercase">@instanceGuid</code>
|
||||
</td>
|
||||
<td>
|
||||
<InstanceStatusText Status="status" />
|
||||
</td>
|
||||
<td>
|
||||
<a href="instances/@instanceGuid" class="btn btn-info btn-sm">Detail</a>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
}
|
||||
@if (instances.IsEmpty) {
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="9">
|
||||
No instances.
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
}
|
||||
</table>
|
||||
<Table TItem="Instance" Items="instances">
|
||||
<HeaderRow>
|
||||
<Column Width="40%">Agent</Column>
|
||||
<Column Width="40%">Name</Column>
|
||||
<Column MinWidth="215px">Status</Column>
|
||||
<Column Width="20%">Version</Column>
|
||||
<Column Class="text-center" MinWidth="110px">Server Port</Column>
|
||||
<Column Class="text-center" MinWidth="110px">Rcon Port</Column>
|
||||
<Column Class="text-end" MinWidth="90px">Memory</Column>
|
||||
<Column MinWidth="75px">Actions</Column>
|
||||
</HeaderRow>
|
||||
<ItemRow Context="instance">
|
||||
@{
|
||||
var configuration = instance.Configuration;
|
||||
var agentName = agentNamesByGuid.TryGetValue(configuration.AgentGuid, out var name) ? name : string.Empty;
|
||||
}
|
||||
<td>
|
||||
<p class="fw-semibold">@agentName</p>
|
||||
<small class="font-monospace text-uppercase">@configuration.AgentGuid.ToString()</small>
|
||||
</td>
|
||||
<td>
|
||||
<p class="fw-semibold">@configuration.InstanceName</p>
|
||||
<small class="font-monospace text-uppercase">@configuration.InstanceGuid.ToString()</small>
|
||||
</td>
|
||||
<td>
|
||||
<InstanceStatusText Status="instance.Status" />
|
||||
</td>
|
||||
<td>@configuration.MinecraftServerKind @configuration.MinecraftVersion</td>
|
||||
<td class="text-center">
|
||||
<p class="font-monospace">@configuration.ServerPort.ToString()</p>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<p class="font-monospace">@configuration.RconPort.ToString()</p>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<p class="font-monospace">@configuration.MemoryAllocation.InMegabytes.ToString() MB</p>
|
||||
</td>
|
||||
<td>
|
||||
<a href="instances/@configuration.InstanceGuid.ToString()" class="btn btn-info btn-sm">Detail</a>
|
||||
</td>
|
||||
</ItemRow>
|
||||
<NoItemsRow>
|
||||
No instances found.
|
||||
</NoItemsRow>
|
||||
</Table>
|
||||
|
||||
@code {
|
||||
|
||||
private ImmutableDictionary<Guid, string> agentNamesByGuid = ImmutableDictionary<Guid, string>.Empty;
|
||||
private ImmutableArray<Instance> instances = ImmutableArray<Instance>.Empty;
|
||||
private ImmutableArray<Instance>? instances;
|
||||
|
||||
protected override void OnInitialized() {
|
||||
AgentManager.AgentsChanged.Subscribe(this, agents => {
|
||||
|
@ -3,7 +3,7 @@
|
||||
@using Phantom.Web.Services.Authentication
|
||||
@using System.ComponentModel.DataAnnotations
|
||||
@attribute [AllowAnonymous]
|
||||
@inject INavigation Navigation
|
||||
@inject Navigation Navigation
|
||||
@inject UserLoginManager LoginManager
|
||||
|
||||
<h1>Login</h1>
|
||||
|
@ -1,5 +1,6 @@
|
||||
@page "/setup"
|
||||
@using Phantom.Utils.Tasks
|
||||
@using Phantom.Web.Services
|
||||
@using Phantom.Web.Services.Authentication
|
||||
@using Phantom.Web.Services.Rpc
|
||||
@using System.ComponentModel.DataAnnotations
|
||||
@ -9,7 +10,7 @@
|
||||
@using Phantom.Common.Data.Web.Users
|
||||
@using Phantom.Common.Data.Web.Users.CreateOrUpdateAdministratorUserResults
|
||||
@attribute [AllowAnonymous]
|
||||
@inject ServiceConfiguration ServiceConfiguration
|
||||
@inject ApplicationProperties ApplicationProperties
|
||||
@inject UserLoginManager LoginManager
|
||||
@inject ControllerConnection ControllerConnection
|
||||
|
||||
@ -83,7 +84,7 @@
|
||||
return false;
|
||||
}
|
||||
|
||||
return CryptographicOperations.FixedTimeEquals(formTokenBytes, ServiceConfiguration.AdministratorToken);
|
||||
return CryptographicOperations.FixedTimeEquals(formTokenBytes, ApplicationProperties.AdministratorToken);
|
||||
}
|
||||
|
||||
private async Task<Result<string>> CreateOrUpdateAdministrator() {
|
||||
|
@ -4,7 +4,7 @@
|
||||
@using Phantom.Common.Data.Web.Users
|
||||
@using Phantom.Web.Services.Users
|
||||
@using Phantom.Web.Services.Authorization
|
||||
@inherits PhantomComponent
|
||||
@inherits Phantom.Web.Components.PhantomComponent
|
||||
@inject UserManager UserManager
|
||||
@inject RoleManager RoleManager
|
||||
@inject UserRoleManager UserRoleManager
|
||||
@ -18,43 +18,36 @@
|
||||
<AuthorizeView>
|
||||
<Authorized>
|
||||
@{ var canEdit = PermissionManager.CheckPermission(context.User, Permission.EditUsers); }
|
||||
<table class="table align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<Column Width="320px">Identifier</Column>
|
||||
<Column Width="125px; 40%">Username</Column>
|
||||
<Column Width="125px; 60%">Roles</Column>
|
||||
@if (canEdit) {
|
||||
<Column Width="175px">Actions</Column>
|
||||
}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var user in allUsers) {
|
||||
var isMe = me == user.Guid;
|
||||
<tr>
|
||||
<td>
|
||||
<code class="text-uppercase">@user.Guid</code>
|
||||
</td>
|
||||
@if (isMe) {
|
||||
<td class="fw-semibold">@user.Name</td>
|
||||
}
|
||||
else {
|
||||
<td>@user.Name</td>
|
||||
}
|
||||
<td>@(userGuidToRoleDescription.TryGetValue(user.Guid, out var roles) ? roles : "?")</td>
|
||||
@if (canEdit) {
|
||||
<td>
|
||||
@if (!isMe) {
|
||||
<button class="btn btn-primary btn-sm" @onclick="() => userRolesDialog.Show(user)">Edit Roles</button>
|
||||
<button class="btn btn-danger btn-sm" @onclick="() => userDeleteDialog.Show(user)">Delete...</button>
|
||||
}
|
||||
</td>
|
||||
}
|
||||
</tr>
|
||||
<Table TItem="UserInfo" Items="allUsers">
|
||||
<HeaderRow>
|
||||
<Column>Username</Column>
|
||||
<Column Width="100%">Roles</Column>
|
||||
@if (canEdit) {
|
||||
<Column MinWidth="175px">Actions</Column>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</HeaderRow>
|
||||
<ItemRow Context="user">
|
||||
@{ var isMe = me == user.Guid; }
|
||||
<td>
|
||||
<p class="fw-semibold">@user.Name</p>
|
||||
<small class="font-monospace text-uppercase">@user.Guid.ToString()</small>
|
||||
</td>
|
||||
<td>
|
||||
@(userGuidToRoleDescription.TryGetValue(user.Guid, out var roles) ? roles : "?")
|
||||
</td>
|
||||
@if (canEdit) {
|
||||
<td>
|
||||
@if (!isMe) {
|
||||
<button class="btn btn-primary btn-sm" @onclick="() => userRolesDialog.Show(user)">Edit Roles</button>
|
||||
<button class="btn btn-danger btn-sm" @onclick="() => userDeleteDialog.Show(user)">Delete...</button>
|
||||
}
|
||||
</td>
|
||||
}
|
||||
</ItemRow>
|
||||
<NoItemsRow>
|
||||
No users found.
|
||||
</NoItemsRow>
|
||||
</Table>
|
||||
</Authorized>
|
||||
</AuthorizeView>
|
||||
|
||||
@ -67,10 +60,12 @@
|
||||
@code {
|
||||
|
||||
private Guid? me = Guid.Empty;
|
||||
private ImmutableArray<UserInfo> allUsers = ImmutableArray<UserInfo>.Empty;
|
||||
private ImmutableArray<UserInfo>? allUsers;
|
||||
private ImmutableDictionary<Guid, RoleInfo> allRolesByGuid = ImmutableDictionary<Guid, RoleInfo>.Empty;
|
||||
private readonly Dictionary<Guid, string> userGuidToRoleDescription = new();
|
||||
private readonly Dictionary<Guid, string> userGuidToRoleDescription = new ();
|
||||
|
||||
private ImmutableArray<UserInfo> AllUsers => allUsers.GetValueOrDefault(ImmutableArray<UserInfo>.Empty);
|
||||
|
||||
private UserRolesDialog userRolesDialog = null!;
|
||||
private UserDeleteDialog userDeleteDialog = null!;
|
||||
|
||||
@ -81,9 +76,10 @@
|
||||
allRolesByGuid = (await RoleManager.GetAll(CancellationToken)).ToImmutableDictionary(static role => role.Guid, static role => role);
|
||||
|
||||
var allUserGuids = allUsers
|
||||
.Select(static user => user.Guid)
|
||||
.ToImmutableHashSet();
|
||||
|
||||
.Value
|
||||
.Select(static user => user.Guid)
|
||||
.ToImmutableHashSet();
|
||||
|
||||
foreach (var (userGuid, roleGuids) in await UserRoleManager.GetUserRoles(allUserGuids, CancellationToken)) {
|
||||
userGuidToRoleDescription[userGuid] = StringifyRoles(roleGuids);
|
||||
}
|
||||
@ -102,7 +98,7 @@
|
||||
}
|
||||
|
||||
private Task OnUserAdded(UserInfo user) {
|
||||
allUsers = allUsers.Add(user);
|
||||
allUsers = AllUsers.Add(user);
|
||||
return RefreshUserRoles(user);
|
||||
}
|
||||
|
||||
@ -111,7 +107,7 @@
|
||||
}
|
||||
|
||||
private void OnUserDeleted(UserInfo user) {
|
||||
allUsers = allUsers.Remove(user);
|
||||
allUsers = AllUsers.Remove(user);
|
||||
userGuidToRoleDescription.Remove(user.Guid);
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,7 @@ using Phantom.Utils.Rpc.Sockets;
|
||||
using Phantom.Utils.Runtime;
|
||||
using Phantom.Utils.Tasks;
|
||||
using Phantom.Web;
|
||||
using Phantom.Web.Services;
|
||||
using Phantom.Web.Services.Rpc;
|
||||
|
||||
var shutdownCancellationTokenSource = new CancellationTokenSource();
|
||||
@ -48,15 +49,15 @@ try {
|
||||
|
||||
var (controllerCertificate, webToken) = webKey.Value;
|
||||
|
||||
var administratorToken = TokenGenerator.Create(60);
|
||||
var applicationProperties = new ApplicationProperties(fullVersion, TokenGenerator.GetBytesOrThrow(administratorToken));
|
||||
|
||||
var rpcConfiguration = new RpcConfiguration("Rpc", controllerHost, controllerPort, controllerCertificate);
|
||||
var rpcSocket = RpcClientSocket.Connect(rpcConfiguration, WebMessageRegistries.Definitions, new RegisterWebMessage(webToken));
|
||||
|
||||
var configuration = new Configuration(PhantomLogger.Create("Web"), webServerHost, webServerPort, webBasePath, dataProtectionKeysPath, shutdownCancellationToken);
|
||||
var administratorToken = TokenGenerator.Create(60);
|
||||
|
||||
var webConfiguration = new WebLauncher.Configuration(PhantomLogger.Create("Web"), webServerHost, webServerPort, webBasePath, dataProtectionKeysPath, shutdownCancellationToken);
|
||||
var taskManager = new TaskManager(PhantomLogger.Create<TaskManager>("Web"));
|
||||
var serviceConfiguration = new ServiceConfiguration(fullVersion, TokenGenerator.GetBytesOrThrow(administratorToken), shutdownCancellationToken);
|
||||
var webApplication = WebLauncher.CreateApplication(configuration, taskManager, serviceConfiguration, rpcSocket.Connection);
|
||||
var webApplication = WebLauncher.CreateApplication(webConfiguration, taskManager, applicationProperties, rpcSocket.Connection);
|
||||
|
||||
MessageListener messageListener;
|
||||
await using (var scope = webApplication.Services.CreateAsyncScope()) {
|
||||
@ -77,9 +78,9 @@ try {
|
||||
|
||||
PhantomLogger.Root.InformationHeading("Launching Phantom Panel web...");
|
||||
PhantomLogger.Root.Information("Your administrator token is: {AdministratorToken}", administratorToken);
|
||||
PhantomLogger.Root.Information("For administrator setup, visit: {HttpUrl}{SetupPath}", configuration.HttpUrl, configuration.BasePath + "setup");
|
||||
PhantomLogger.Root.Information("For administrator setup, visit: {HttpUrl}{SetupPath}", webConfiguration.HttpUrl, webConfiguration.BasePath + "setup");
|
||||
|
||||
await WebLauncher.Launch(configuration, webApplication);
|
||||
await WebLauncher.Launch(webConfiguration, webApplication);
|
||||
} finally {
|
||||
shutdownCancellationTokenSource.Cancel();
|
||||
await taskManager.Stop();
|
||||
|
@ -1,7 +0,0 @@
|
||||
namespace Phantom.Web;
|
||||
|
||||
public sealed record ServiceConfiguration(
|
||||
string Version,
|
||||
byte[] AdministratorToken,
|
||||
CancellationToken CancellationToken
|
||||
);
|
@ -15,8 +15,8 @@
|
||||
@using Phantom.Web.Services.Agents
|
||||
@using Phantom.Web.Services.Instances
|
||||
@using Phantom.Web.Services.Rpc
|
||||
@inherits PhantomComponent
|
||||
@inject INavigation Nav
|
||||
@inherits Phantom.Web.Components.PhantomComponent
|
||||
@inject Navigation Navigation
|
||||
@inject ControllerConnection ControllerConnection
|
||||
@inject AgentManager AgentManager
|
||||
@inject InstanceManager InstanceManager
|
||||
@ -342,7 +342,7 @@
|
||||
|
||||
var result = await InstanceManager.CreateOrUpdateInstance(loggedInUserGuid.Value, instance, CancellationToken);
|
||||
if (result.Is(CreateOrUpdateInstanceResult.Success)) {
|
||||
await Nav.NavigateTo("instances/" + instance.InstanceGuid);
|
||||
await Navigation.NavigateTo("instances/" + instance.InstanceGuid);
|
||||
}
|
||||
else {
|
||||
form.SubmitModel.StopSubmitting(result.ToSentence(CreateOrUpdateInstanceResultExtensions.ToSentence));
|
||||
|
@ -1,7 +1,7 @@
|
||||
@using Phantom.Web.Services.Instances
|
||||
@using Phantom.Common.Data.Web.Users
|
||||
@using Phantom.Common.Data.Replies
|
||||
@inherits PhantomComponent
|
||||
@inherits Phantom.Web.Components.PhantomComponent
|
||||
@inject InstanceManager InstanceManager
|
||||
|
||||
<Form Model="form" OnSubmit="ExecuteCommand">
|
||||
|
@ -3,7 +3,7 @@
|
||||
@using System.Diagnostics
|
||||
@using Phantom.Web.Services.Instances
|
||||
@using Phantom.Common.Data.Web.Users
|
||||
@inherits PhantomComponent
|
||||
@inherits Phantom.Web.Components.PhantomComponent
|
||||
@inject IJSRuntime Js;
|
||||
@inject InstanceLogManager InstanceLogManager;
|
||||
|
||||
|
@ -1,46 +1,48 @@
|
||||
@using Phantom.Common.Data.Instance
|
||||
<nobr>
|
||||
@switch (Status) {
|
||||
case InstanceIsOffline:
|
||||
<text>Offline</text>
|
||||
<span class="fw-semibold">Offline</span>
|
||||
break;
|
||||
|
||||
case InstanceIsInvalid invalid:
|
||||
<text>Invalid <sup title="@invalid.Reason">[?]</sup></text>
|
||||
<span class="fw-semibold text-danger">Invalid <sup title="@invalid.Reason">[?]</sup></span>
|
||||
break;
|
||||
|
||||
case InstanceIsNotRunning:
|
||||
<text>Not Running</text>
|
||||
<span class="fw-semibold">Not Running</span>
|
||||
break;
|
||||
|
||||
case InstanceIsDownloading downloading:
|
||||
<ProgressBar Value="@downloading.Progress" Maximum="100">
|
||||
Downloading Server (@downloading.Progress%)
|
||||
<span class="fw-semibold">Downloading Server</span> (@downloading.Progress%)
|
||||
</ProgressBar>
|
||||
break;
|
||||
|
||||
case InstanceIsLaunching:
|
||||
<div class="spinner-border spinner-border-sm" role="status"></div>
|
||||
<text> Launching</text>
|
||||
<div class="spinner-border" role="status"></div>
|
||||
<span class="fw-semibold"> Launching</span>
|
||||
break;
|
||||
|
||||
case InstanceIsRunning:
|
||||
<text>Running</text>
|
||||
<span class="fw-semibold text-success">Running</span>
|
||||
break;
|
||||
|
||||
case InstanceIsRestarting:
|
||||
<div class="spinner-border spinner-border-sm" role="status"></div>
|
||||
<text> Restarting</text>
|
||||
<div class="spinner-border" role="status"></div>
|
||||
<span class="fw-semibold"> Restarting</span>
|
||||
break;
|
||||
|
||||
case InstanceIsStopping:
|
||||
<div class="spinner-border spinner-border-sm" role="status"></div>
|
||||
<text> Stopping</text>
|
||||
<div class="spinner-border" role="status"></div>
|
||||
<span class="fw-semibold"> Stopping</span>
|
||||
break;
|
||||
|
||||
case InstanceIsFailed failed:
|
||||
<text>Failed <sup title="@failed.Reason.ToSentence()">[?]</sup></text>
|
||||
<span class="fw-semibold text-danger">Failed <sup title="@failed.Reason.ToSentence()">[?]</sup></span>
|
||||
break;
|
||||
}
|
||||
</nobr>
|
||||
|
||||
@code {
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
@using Phantom.Common.Data.Web.Users
|
||||
@using Phantom.Common.Data.Minecraft
|
||||
@using Phantom.Common.Data.Replies
|
||||
@inherits PhantomComponent
|
||||
@inherits Phantom.Web.Components.PhantomComponent
|
||||
@inject IJSRuntime Js;
|
||||
@inject InstanceManager InstanceManager;
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
@using Phantom.Common.Data.Web.Users.CreateUserResults
|
||||
@using Phantom.Web.Services.Users
|
||||
@using System.ComponentModel.DataAnnotations
|
||||
@inherits PhantomComponent
|
||||
@inherits Phantom.Web.Components.PhantomComponent
|
||||
@inject IJSRuntime Js;
|
||||
@inject UserManager UserManager;
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.JSInterop;
|
||||
using Phantom.Common.Data.Web.Users;
|
||||
using Phantom.Web.Base;
|
||||
using Phantom.Web.Components;
|
||||
using Phantom.Web.Components.Forms;
|
||||
|
||||
namespace Phantom.Web.Shared;
|
||||
|
@ -2,14 +2,18 @@
|
||||
using Phantom.Common.Messages.Web;
|
||||
using Phantom.Utils.Rpc.Runtime;
|
||||
using Phantom.Utils.Tasks;
|
||||
using Phantom.Web.Base;
|
||||
using Phantom.Web.Services;
|
||||
using Serilog;
|
||||
using ILogger = Serilog.ILogger;
|
||||
|
||||
namespace Phantom.Web;
|
||||
|
||||
static class WebLauncher {
|
||||
public static WebApplication CreateApplication(Configuration config, TaskManager taskManager, ServiceConfiguration serviceConfiguration, RpcConnectionToServer<IMessageToControllerListener> controllerConnection) {
|
||||
internal sealed record Configuration(ILogger Logger, string Host, ushort Port, string BasePath, string DataProtectionKeyFolderPath, CancellationToken CancellationToken) {
|
||||
public string HttpUrl => "http://" + Host + ":" + Port;
|
||||
}
|
||||
|
||||
internal static WebApplication CreateApplication(Configuration config, TaskManager taskManager, ApplicationProperties applicationProperties, RpcConnectionToServer<IMessageToControllerListener> controllerConnection) {
|
||||
var assembly = typeof(WebLauncher).Assembly;
|
||||
var builder = WebApplication.CreateBuilder(new WebApplicationOptions {
|
||||
ApplicationName = assembly.GetName().Name,
|
||||
@ -26,12 +30,12 @@ static class WebLauncher {
|
||||
}
|
||||
|
||||
builder.Services.AddSingleton(taskManager);
|
||||
builder.Services.AddSingleton(serviceConfiguration);
|
||||
builder.Services.AddSingleton(applicationProperties);
|
||||
builder.Services.AddSingleton(controllerConnection);
|
||||
builder.Services.AddPhantomServices();
|
||||
|
||||
builder.Services.AddSingleton<IHostLifetime>(new NullLifetime());
|
||||
builder.Services.AddScoped<INavigation>(Navigation.Create(config.BasePath));
|
||||
builder.Services.AddScoped(Navigation.Create(config.BasePath));
|
||||
|
||||
builder.Services.AddDataProtection().PersistKeysToFileSystem(new DirectoryInfo(config.DataProtectionKeyFolderPath));
|
||||
|
||||
@ -41,7 +45,7 @@ static class WebLauncher {
|
||||
return builder.Build();
|
||||
}
|
||||
|
||||
public static Task Launch(Configuration config, WebApplication application) {
|
||||
internal static Task Launch(Configuration config, WebApplication application) {
|
||||
var logger = config.Logger;
|
||||
|
||||
application.UseSerilogRequestLogging();
|
||||
|
@ -6,8 +6,7 @@
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
@using Microsoft.AspNetCore.Components.Web.Virtualization
|
||||
@using Microsoft.JSInterop
|
||||
@using Phantom.Web
|
||||
@using Phantom.Web.Base
|
||||
@using Phantom.Web.Components
|
||||
@using Phantom.Web.Components.Dialogs
|
||||
@using Phantom.Web.Components.Forms
|
||||
@using Phantom.Web.Components.Graphics
|
||||
|
@ -14,6 +14,8 @@
|
||||
|
||||
.sidebar {
|
||||
background-image: linear-gradient(180deg, #0f6477 200px, #1b3d59 1000px);
|
||||
border-right: 1px solid rgba(2, 39, 47, 0.5);
|
||||
box-shadow: 0 0 2px #093c47;
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
@ -26,16 +28,22 @@
|
||||
flex-direction: column;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
width: 250px;
|
||||
min-height: 100vh;
|
||||
width: 230px;
|
||||
height: 100vh;
|
||||
}
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
padding: 1.1rem 1.5rem 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
h1:focus {
|
||||
outline: none;
|
||||
}
|
||||
@ -51,14 +59,30 @@ code {
|
||||
|
||||
.table {
|
||||
margin-top: 0.5rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.table > :not(:first-child) {
|
||||
border-top: 2px solid #a6a6a6;
|
||||
}
|
||||
|
||||
.table > :not(caption) > * > * {
|
||||
padding: 0.5rem 0.75rem;
|
||||
.table th, .table td {
|
||||
padding: 0.5rem 1.25rem;
|
||||
}
|
||||
|
||||
.table p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.table small {
|
||||
display: block;
|
||||
font-weight: normal;
|
||||
font-size: 0.825rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.table small.font-monospace {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.form-range {
|
||||
@ -91,9 +115,19 @@ code {
|
||||
|
||||
.text-condensed {
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.05rem;
|
||||
line-height: 1.15rem;
|
||||
}
|
||||
|
||||
.text-condensed code {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.horizontal-scroll {
|
||||
overflow-x: auto;
|
||||
scrollbar-width: thin;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.horizontal-scroll > .table {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user