[backend/razor] Add a navigation bar to the admin dashboard
This is implemented using a reusable navigation bar component.
This commit is contained in:
parent
7841bfa1ba
commit
346803935a
15 changed files with 352 additions and 19 deletions
|
@ -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>
|
20
Iceshrimp.Backend/Components/Admin/AdminNav.razor
Normal file
20
Iceshrimp.Backend/Components/Admin/AdminNav.razor
Normal 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)];
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
|
147
Iceshrimp.Backend/Components/Generic/NavBar.razor
Normal file
147
Iceshrimp.Backend/Components/Generic/NavBar.razor
Normal 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;
|
||||||
|
}
|
146
Iceshrimp.Backend/Components/Generic/NavBar.razor.css
Normal file
146
Iceshrimp.Backend/Components/Generic/NavBar.razor.css
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
11
Iceshrimp.Backend/Components/Generic/NavBar.razor.js
Normal file
11
Iceshrimp.Backend/Components/Generic/NavBar.razor.js
Normal 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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
18
Iceshrimp.Backend/Components/Generic/NavBarLink.razor
Normal file
18
Iceshrimp.Backend/Components/Generic/NavBarLink.razor
Normal 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";
|
||||||
|
}
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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"/>
|
||||||
|
|
|
@ -14,3 +14,7 @@ p {
|
||||||
.width30 {
|
.width30 {
|
||||||
width: 30ch;
|
width: 30ch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin-top: 60px;
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue