[backend/razor] Add a navigation bar to the admin dashboard

This is implemented using a reusable navigation bar component.
This commit is contained in:
Laura Hausmann 2024-11-06 03:41:23 +01:00
parent 7841bfa1ba
commit 346803935a
No known key found for this signature in database
GPG key ID: D044E84C5BE01605
15 changed files with 352 additions and 19 deletions

View file

@ -2,5 +2,6 @@
@using Iceshrimp.Backend.Components.Helpers @using Iceshrimp.Backend.Components.Helpers
<HeadContent> <HeadContent>
<VersionedLink rel="stylesheet" href="/css/admin.css"/> <VersionedLink rel="stylesheet" href="/css/admin.css"/>
<VersionedLink rel="stylesheet" href="/_content/Iceshrimp.Assets.PhosphorIcons/css/ph-regular.css"/>
<VersionedScript src="/js/admin.js"/> <VersionedScript src="/js/admin.js"/>
</HeadContent> </HeadContent>

View file

@ -0,0 +1,20 @@
@using Iceshrimp.Assets.PhosphorIcons
@using Iceshrimp.Backend.Components.Generic
<NavBar Brand="_brand" Links="_links" Right="_right" MaxItemsLg="7" MaxItemsMd="3"/>
@code
{
private NavBar.NavLink _brand = new("/admin", "Admin Dashboard");
private List<NavBar.NavLink> _links =
[
new("/admin", "Overview", Icons.ChartLine), // spacer for alignment
new("/admin/metadata", "Instance metadata", Icons.Info),
new("/admin/users", "User management", Icons.Users),
new("/admin/federation", "Federation control", Icons.Graph),
new("/admin/relays", "Relays", Icons.FastForward),
new("/admin/plugins", "Plugins", Icons.Plug)
];
private List<NavBar.NavLink> _right = [new("/queue", "Queue dashboard", IconRight: Icons.ArrowSquareOut, NewTab: true)];
}

View file

@ -1,7 +1,7 @@
@using Microsoft.AspNetCore.Components.Web @using Microsoft.AspNetCore.Components.Web
<PageTitle>@Title - Admin - @(InstanceName ?? "Iceshrimp.NET")</PageTitle> <PageTitle>@Title - Admin - @(InstanceName ?? "Iceshrimp.NET")</PageTitle>
<h1>Admin Dashboard</h1> <AdminHead/>
<button role="link" data-target="/admin" onclick="navigate(event)">Return to overview</button> <AdminNav/>
<h2>@Title</h2> <h2>@Title</h2>
@code { @code {

View file

@ -0,0 +1,147 @@
@using Iceshrimp.Assets.PhosphorIcons
@using Iceshrimp.Backend.Components.Helpers
<nav class="navbar navbar-lg">
<ul>
<li>
<a href="@Brand.Href" class="brand">@Brand.Name</a>
</li>
</ul>
<ul>
@foreach (var link in Links[..Math.Min(MaxItemsLg, Links.Count)])
{
<li>
<NavBarLink Link="link"/>
</li>
}
@if (OverflowsLg)
{
var offset = Links.Count - MaxItemsLg;
<li class="dropdown">
<a class="dropdown-button" tabindex="0">
<Icon Name="Icons.DotsThree" Size="20pt"/>
</a>
<ul class="dropdown-menu">
@foreach (var link in Links[offset..])
{
<li>
<NavBarLink Link="link"/>
</li>
}
@if (Right is { Count: > 0 })
{
<li class="dropdown-spacer"></li>
@foreach (var link in Right)
{
<li>
<NavBarLink Link="link"/>
</li>
}
}
</ul>
</li>
}
</ul>
@if (!OverflowsLg)
{
<ul class="nav-right">
@if (Right is { Count: > 0 })
{
foreach (var link in Right)
{
<li>
<NavBarLink Link="link"/>
</li>
}
}
</ul>
}
</nav>
<nav class="navbar navbar-md">
<ul>
<li>
<a href="@Brand.Href" class="brand">@Brand.Name</a>
</li>
</ul>
<ul>
@foreach (var link in Links[..Math.Min(MaxItemsMd, Links.Count)])
{
<li>
<NavBarLink Link="link"/>
</li>
}
@if (OverflowsMd)
{
var offset = Links.Count - MaxItemsMd;
<li class="dropdown">
<a class="dropdown-button" tabindex="0">
<Icon Name="Icons.DotsThree" Size="20pt"/>
</a>
<ul class="dropdown-menu">
@foreach (var link in Links[offset..])
{
<li>
<NavBarLink Link="link"/>
</li>
}
@if (Right is { Count: > 0 })
{
<li class="dropdown-spacer"></li>
@foreach (var link in Right)
{
<li>
<NavBarLink Link="link"/>
</li>
}
}
</ul>
</li>
}
</ul>
</nav>
<nav class="navbar navbar-sm">
<ul>
<li>
<a href="@Brand.Href" class="brand">@Brand.Name</a>
</li>
</ul>
<ul class="nav-right">
<li>
<a class="hamburger-button" href="#" onclick="toggleHamburger(event)">
<Icon Name="Icons.List" Size="20pt"/>
</a>
<ul class="hamburger-menu hidden">
@foreach (var link in Links)
{
<li>
<NavBarLink Link="link"/>
</li>
}
@if (Right is { Count: > 0 })
{
<li class="hamburger-spacer"></li>
@foreach (var link in Right)
{
<li>
<NavBarLink Link="link"/>
</li>
}
}
</ul>
</li>
</ul>
</nav>
<VersionedScript src="/Components/Generic/NavBar.razor.js"/>
@code {
public record struct NavLink(string Href, string Name, IconName? Icon = null, IconName? IconRight = null, bool NewTab = false);
[Parameter, EditorRequired] public required int MaxItemsLg { get; set; }
[Parameter, EditorRequired] public required int MaxItemsMd { get; set; }
[Parameter, EditorRequired] public required NavLink Brand { get; set; }
[Parameter, EditorRequired] public required List<NavLink> Links { get; set; }
[Parameter] public List<NavLink>? Right { get; set; }
private bool OverflowsLg => Links.Count + (Right?.Count ?? 0) > MaxItemsLg;
private bool OverflowsMd => Links.Count + (Right?.Count ?? 0) > MaxItemsMd;
}

View file

@ -0,0 +1,146 @@
.navbar {
position: absolute;
left: 0;
top: 0;
right: 0;
height: 50px;
padding: 0;
background-color: var(--button-base);
display: flex;
align-items: center;
white-space: nowrap;
vertical-align: middle;
}
a {
text-decoration: none;
}
.navbar ::deep a {
color: var(--text-main);
}
.navbar ul {
margin: 0;
padding: 0;
height: 100%;
list-style-type: none;
display: flex;
align-items: center;
}
.navbar .brand {
color: var(--text-bright);
padding: 0 15px;
font-weight: bold;
}
.navbar ul.nav-right:last-of-type li:last-of-type ::deep a {
padding: 0 15px;
}
.navbar ul li {
height: 100%;
}
.navbar ul li ::deep a {
color: var(--text-main);
padding: 0 12px;
height: 100%;
display: flex;
align-items: center;
justify-content: flex-start;
gap: 4pt;
}
.navbar ::deep a.active,
.navbar ul ::deep a:hover,
.navbar ul ::deep a:focus,
.navbar .brand:hover,
.navbar .brand:focus {
background-color: var(--button-hover);
text-decoration: none;
}
.navbar ul.nav-right {
margin-left: auto;
}
.dropdown-button {
cursor: pointer;
}
.dropdown:hover > .dropdown-menu,
.dropdown:focus-within > .dropdown-menu,
.dropdown-menu:hover,
.dropdown-menu:focus {
visibility: visible;
opacity: 1;
display: block !important;
}
.dropdown {
position: relative;
}
.dropdown-menu {
opacity: 0;
min-width: 5rem;
position: absolute;
margin-top: 1rem;
left: 0;
z-index: +1;
display: none !important;
}
ul.dropdown-menu li {
background-color: var(--button-base);
}
li.dropdown-spacer,
li.hamburger-spacer {
height: 1px !important;
border-top: 1px solid var(--border);
filter: brightness(65%);
}
.hamburger-button {
cursor: pointer;
}
.hamburger-menu.hidden {
display: none !important;
}
.hamburger-menu {
position: absolute;
right: 0;
display: inline-block !important;
z-index: +1;
}
ul.hamburger-menu li {
display: block !important;
background-color: var(--button-base);
}
.navbar-lg {
display: none;
@media screen and (min-width: 1200px) {
display: flex;
}
}
.navbar-md {
display: none;
@media screen and (min-width: 800px) and (max-width: 1199px) {
display: flex;
}
}
.navbar-sm {
display: none;
@media screen and (max-width: 799px) {
display: flex;
}
}

View file

@ -0,0 +1,11 @@
function toggleHamburger(e) {
e.preventDefault()
for (let el of document.getElementsByClassName("hamburger-menu")) {
if (el.classList.contains("hidden")) {
el.classList.remove('hidden');
} else {
el.classList.add('hidden');
}
}
}

View file

@ -0,0 +1,18 @@
@using Microsoft.AspNetCore.Components.Routing
@using Iceshrimp.Assets.PhosphorIcons
<NavLink href="@Link.Href" class="nav-link" Match="NavLinkMatch.All" target="@Target">
@if (Link.Icon != null)
{
<Icon Name="Link.Icon"/>
}
@Link.Name
@if (Link.IconRight != null)
{
<Icon Name="Link.IconRight"/>
}
</NavLink>
@code {
[Parameter, EditorRequired] public required NavBar.NavLink Link { get; set; }
private string Target => Link.NewTab ? "_blank" : "_self";
}

View file

@ -9,7 +9,6 @@
@using static Iceshrimp.Backend.Core.Configuration.Enums; @using static Iceshrimp.Backend.Core.Configuration.Enums;
@using Microsoft.AspNetCore.Components.Forms @using Microsoft.AspNetCore.Components.Forms
@inherits AdminComponentBase @inherits AdminComponentBase
<AdminHead/>
<AdminPageHeader Title="@($"{ModeString} instances")"/> <AdminPageHeader Title="@($"{ModeString} instances")"/>
<table> <table>
<thead> <thead>

View file

@ -5,7 +5,6 @@
@using Microsoft.AspNetCore.Components.Forms @using Microsoft.AspNetCore.Components.Forms
@inherits AdminComponentBase @inherits AdminComponentBase
<AdminHead/>
<AdminPageHeader Title="Instance metadata"/> <AdminPageHeader Title="Instance metadata"/>
<p>Here you can adjust basic instance metadata. It gets displayed to all users, including guests.</p> <p>Here you can adjust basic instance metadata. It gets displayed to all users, including guests.</p>

View file

@ -4,20 +4,10 @@
@using Microsoft.EntityFrameworkCore @using Microsoft.EntityFrameworkCore
@inherits AdminComponentBase @inherits AdminComponentBase
<PageTitle>Overview - Admin - @(InstanceName ?? "Iceshrimp.NET")</PageTitle> <AdminPageHeader Title="Overview"/>
<AdminHead/> <p>This interface is used to adjust parameters of this Iceshrimp.NET instance.</p>
<h1>Admin Dashboard</h1>
<p>This interface is used to adjust parameters of this Iceshrimp.NET instance. Please pick a category below.</p>
<button role="link" data-target="/admin/metadata" onclick="navigate(event)">Instance metadata</button>
<button role="link" data-target="/admin/users" onclick="navigate(event)">User management</button>
<button role="link" data-target="/admin/federation" onclick="navigate(event)">Federation control</button>
<button role="link" data-target="/admin/relays" onclick="navigate(event)">Relays</button>
<button role="link" data-target="/admin/plugins" onclick="navigate(event)">Plugins</button>
<button role="link" data-target="/queue" onclick="navigate(event)">[ext] Queue dashboard</button>
@* @*
- TODO: Improve styles, possibly add a navbar
- TODO: Remote user management - TODO: Remote user management
- TODO: More federation stats (e.g. # of activities sent/fetched, some basic queue stats) - TODO: More federation stats (e.g. # of activities sent/fetched, some basic queue stats)
- TODO: Move queue dashboard to blazor ssr - TODO: Move queue dashboard to blazor ssr

View file

@ -2,7 +2,6 @@
@using Iceshrimp.Backend.Components.Admin @using Iceshrimp.Backend.Components.Admin
@using Iceshrimp.Backend.Core.Helpers @using Iceshrimp.Backend.Core.Helpers
@inherits AdminComponentBase @inherits AdminComponentBase
<AdminHead/>
<AdminPageHeader Title="Plugins"/> <AdminPageHeader Title="Plugins"/>
<table> <table>

View file

@ -9,7 +9,6 @@
@using Microsoft.Extensions.Options @using Microsoft.Extensions.Options
@using Microsoft.AspNetCore.Components.Forms @using Microsoft.AspNetCore.Components.Forms
@inherits AdminComponentBase @inherits AdminComponentBase
<AdminHead/>
<AdminPageHeader Title="Relays"/> <AdminPageHeader Title="Relays"/>
@if (!Options.Value.AcceptLdSignatures) @if (!Options.Value.AcceptLdSignatures)

View file

@ -5,7 +5,6 @@
@using Microsoft.EntityFrameworkCore @using Microsoft.EntityFrameworkCore
@using Microsoft.Extensions.Options @using Microsoft.Extensions.Options
@inherits AdminComponentBase @inherits AdminComponentBase
<AdminHead/>
<AdminPageHeader Title="Users"/> <AdminPageHeader Title="Users"/>
<table> <table>

View file

@ -14,6 +14,7 @@
<!--suppress HtmlRequiredTitleElement, Justification: HeadOutlet --> <!--suppress HtmlRequiredTitleElement, Justification: HeadOutlet -->
<head> <head>
<meta charset="utf-8"/> <meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<VersionedLink rel="stylesheet" href="/Iceshrimp.Backend.styles.css"/> <VersionedLink rel="stylesheet" href="/Iceshrimp.Backend.styles.css"/>
<VersionedLink rel="stylesheet" href="/css/default.css"/> <VersionedLink rel="stylesheet" href="/css/default.css"/>
<VersionedLink rel="icon" type="image/png" href="/favicon.png"/> <VersionedLink rel="icon" type="image/png" href="/favicon.png"/>

View file

@ -14,3 +14,7 @@ p {
.width30 { .width30 {
width: 30ch; width: 30ch;
} }
body {
margin-top: 60px;
}