diff --git a/Web/Phantom.Web.Components/Tables/Cell.razor b/Web/Phantom.Web.Components/Tables/Cell.razor new file mode 100644 index 0000000..19ca5e1 --- /dev/null +++ b/Web/Phantom.Web.Components/Tables/Cell.razor @@ -0,0 +1,23 @@ +@if (Url == null) { + <td @attributes="AdditionalAttributes"> + @ChildContent + </td> +} +else { + <td class="p-0" @attributes="AdditionalAttributes"> + <a class="table-link" href="@Url">@ChildContent</a> + </td> +} + +@code { + + [CascadingParameter(Name = "Url")] + public string? Url { get; set; } + + [Parameter] + public RenderFragment? ChildContent { get; set; } + + [Parameter(CaptureUnmatchedValues = true)] + public IReadOnlyDictionary<string, object>? AdditionalAttributes { get; set; } + +} diff --git a/Web/Phantom.Web.Components/Tables/Table.razor b/Web/Phantom.Web.Components/Tables/Table.razor index 3cf3ca6..e4dab25 100644 --- a/Web/Phantom.Web.Components/Tables/Table.razor +++ b/Web/Phantom.Web.Components/Tables/Table.razor @@ -1,7 +1,7 @@ @typeparam TItem <div class="horizontal-scroll"> - <table class="table align-middle@(Class.Length == 0 ? "" : " " + Class)"> + <table class="@FullClass"> <thead> <tr> @@ -22,7 +22,9 @@ <tbody> @foreach (var item in Items) { <tr> - @ItemRow(item) + <CascadingValue Name="Url" Value="@ItemUrl?.Invoke(item)"> + @ItemRow(item) + </CascadingValue> </tr> } </tbody> @@ -43,6 +45,25 @@ [Parameter] public string Class { get; set; } = string.Empty; + private string FullClass { + get { + List<string> classes = new (4) { + "table", + "align-middle" + }; + + if (ItemUrl != null) { + classes.Add("table-hover"); + } + + if (Class.Length > 0) { + classes.Add(Class); + } + + return string.Join(' ', classes); + } + } + [Parameter, EditorRequired] public RenderFragment HeaderRow { get; set; } = null!; @@ -55,4 +76,7 @@ [Parameter, EditorRequired] public IReadOnlyList<TItem>? Items { get; set; } + [Parameter] + public Func<TItem, string>? ItemUrl { get; set; } = null; + } diff --git a/Web/Phantom.Web/Pages/Agents.razor b/Web/Phantom.Web/Pages/Agents.razor index 035e431..6adf9fa 100644 --- a/Web/Phantom.Web/Pages/Agents.razor +++ b/Web/Phantom.Web/Pages/Agents.razor @@ -21,39 +21,39 @@ var usedInstances = agent.Stats?.RunningInstanceCount; var usedMemory = agent.Stats?.RunningInstanceMemory.InMegabytes; } - <td> + <Cell> <p class="fw-semibold">@agent.Name</p> <small class="font-monospace text-uppercase">@agent.Guid.ToString()</small> - </td> - <td class="text-end"> + </Cell> + <Cell class="text-end"> <ProgressBar Value="@(usedInstances ?? 0)" Maximum="@agent.MaxInstances"> @(usedInstances?.ToString() ?? "?") / @agent.MaxInstances.ToString() </ProgressBar> - </td> - <td class="text-end"> + </Cell> + <Cell class="text-end"> <ProgressBar Value="@(usedMemory ?? 0)" Maximum="@agent.MaxMemory.InMegabytes"> @(usedMemory?.ToString() ?? "?") / @agent.MaxMemory.InMegabytes.ToString() MB </ProgressBar> - </td> - <td class="text-condensed"> + </Cell> + <Cell class="text-condensed"> Build: <span class="font-monospace">@agent.BuildVersion</span> <br> Protocol: <span class="font-monospace">v@(agent.ProtocolVersion.ToString())</span> - </td> + </Cell> @if (agent.IsOnline) { - <td class="fw-semibold text-center text-success">Online</td> - <td class="text-end">-</td> + <Cell class="fw-semibold text-center text-success">Online</Cell> + <Cell class="text-end">-</Cell> } else { - <td class="fw-semibold text-center">Offline</td> - <td class="text-end"> + <Cell class="fw-semibold text-center">Offline</Cell> + <Cell class="text-end"> @if (agent.LastPing is {} lastPing) { <TimeWithOffset Time="lastPing" /> } else { <text>N/A</text> } - </td> + </Cell> } </ItemRow> <NoItemsRow> diff --git a/Web/Phantom.Web/Pages/Audit.razor b/Web/Phantom.Web/Pages/Audit.razor index f12b93c..c27477f 100644 --- a/Web/Phantom.Web/Pages/Audit.razor +++ b/Web/Phantom.Web/Pages/Audit.razor @@ -21,23 +21,23 @@ <Column Width="100%">Data</Column> </HeaderRow> <ItemRow Context="logItem"> - <td class="text-end"> + <Cell class="text-end"> <TimeWithOffset Time="logItem.UtcTime.ToLocalTime()" /> - </td> - <td> + </Cell> + <Cell> <p class="fw-semibold">@(logItem.UserName ?? "-")</p> <small class="font-monospace text-uppercase">@logItem.UserGuid.ToString()</small> - </td> - <td> + </Cell> + <Cell> <p>@logItem.EventType.ToNiceString()</p> - </td> - <td> + </Cell> + <Cell> <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> + </Cell> + <Cell> <code>@logItem.JsonData</code> - </td> + </Cell> </ItemRow> <NoItemsRow> No audit log entries found. diff --git a/Web/Phantom.Web/Pages/Events.razor b/Web/Phantom.Web/Pages/Events.razor index 677a6cd..575ff4c 100644 --- a/Web/Phantom.Web/Pages/Events.razor +++ b/Web/Phantom.Web/Pages/Events.razor @@ -22,10 +22,10 @@ <Column Width="100%">Data</Column> </HeaderRow> <ItemRow Context="logItem"> - <td class="text-end"> + <Cell class="text-end"> <TimeWithOffset Time="logItem.UtcTime.ToLocalTime()" /> - </td> - <td> + </Cell> + <Cell> @if (logItem.AgentGuid is {} agentGuid) { <p class="fw-semibold">@(GetAgentName(agentGuid))</p> <small class="font-monospace text-uppercase">@agentGuid.ToString()</small> @@ -33,15 +33,15 @@ else { <text>-</text> } - </td> - <td>@logItem.EventType.ToNiceString()</td> - <td> + </Cell> + <Cell>@logItem.EventType.ToNiceString()</Cell> + <Cell> <p class="fw-semibold">@(GetSubjectName(logItem.SubjectType, logItem.SubjectId) ?? "-")</p> <small class="font-monospace text-uppercase">@(logItem.SubjectId)</small> - </td> - <td> + </Cell> + <Cell> <code>@logItem.JsonData</code> - </td> + </Cell> </ItemRow> <NoItemsRow> No event log entries found. diff --git a/Web/Phantom.Web/Pages/Instances.razor b/Web/Phantom.Web/Pages/Instances.razor index 6a1db4a..d39cf9a 100644 --- a/Web/Phantom.Web/Pages/Instances.razor +++ b/Web/Phantom.Web/Pages/Instances.razor @@ -16,7 +16,7 @@ <a href="instances/create" class="btn btn-primary" role="button">New Instance</a> </PermissionView> -<Table TItem="Instance" Items="instances"> +<Table TItem="Instance" Items="instances" ItemUrl="@(static instance => "instances/" + instance.Configuration.InstanceGuid)"> <HeaderRow> <Column Width="40%">Agent</Column> <Column Width="40%">Name</Column> @@ -28,34 +28,31 @@ <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> + @{ var configuration = instance.Configuration; } + <Cell> + <p class="fw-semibold">@(agentNamesByGuid.TryGetValue(configuration.AgentGuid, out var name) ? name : string.Empty)</p> <small class="font-monospace text-uppercase">@configuration.AgentGuid.ToString()</small> - </td> - <td> + </Cell> + <Cell> <p class="fw-semibold">@configuration.InstanceName</p> <small class="font-monospace text-uppercase">@configuration.InstanceGuid.ToString()</small> - </td> - <td> + </Cell> + <Cell> <InstanceStatusText Status="instance.Status" /> - </td> - <td>@configuration.MinecraftServerKind @configuration.MinecraftVersion</td> - <td class="text-center"> + </Cell> + <Cell>@configuration.MinecraftServerKind @configuration.MinecraftVersion</Cell> + <Cell class="text-center"> <p class="font-monospace">@configuration.ServerPort.ToString()</p> - </td> - <td class="text-center"> + </Cell> + <Cell class="text-center"> <p class="font-monospace">@configuration.RconPort.ToString()</p> - </td> - <td class="text-end"> + </Cell> + <Cell class="text-end"> <p class="font-monospace">@configuration.MemoryAllocation.InMegabytes.ToString() MB</p> - </td> - <td> + </Cell> + <Cell> <a href="instances/@configuration.InstanceGuid.ToString()" class="btn btn-info btn-sm">Detail</a> - </td> + </Cell> </ItemRow> <NoItemsRow> No instances found. diff --git a/Web/Phantom.Web/Pages/Users.razor b/Web/Phantom.Web/Pages/Users.razor index dd0eb25..8d82028 100644 --- a/Web/Phantom.Web/Pages/Users.razor +++ b/Web/Phantom.Web/Pages/Users.razor @@ -28,20 +28,20 @@ </HeaderRow> <ItemRow Context="user"> @{ var isMe = me == user.Guid; } - <td> + <Cell> <p class="fw-semibold">@user.Name</p> <small class="font-monospace text-uppercase">@user.Guid.ToString()</small> - </td> - <td> + </Cell> + <Cell> @(userGuidToRoleDescription.TryGetValue(user.Guid, out var roles) ? roles : "?") - </td> + </Cell> @if (canEdit) { - <td> + <Cell> @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> + </Cell> } </ItemRow> <NoItemsRow> diff --git a/Web/Phantom.Web/wwwroot/css/site.css b/Web/Phantom.Web/wwwroot/css/site.css index b833ef2..734d141 100644 --- a/Web/Phantom.Web/wwwroot/css/site.css +++ b/Web/Phantom.Web/wwwroot/css/site.css @@ -60,16 +60,23 @@ code { .table { margin-top: 0.5rem; white-space: nowrap; + --bs-table-hover-bg: rgba(15, 100, 119, 0.05); } .table > :not(:first-child) { border-top: 2px solid #a6a6a6; } -.table th, .table td { +.table th, .table td, .table td > .table-link { padding: 0.5rem 1.25rem; } +.table .table-link { + display: block; + color: inherit; + text-decoration: none; +} + .table p { margin: 0; }