Implemented permissions. Added user overview and session overview. Added lazy loader.. Did some ui
This commit is contained in:
parent
0cde0fe302
commit
96bd131807
16 changed files with 388 additions and 19 deletions
|
@ -0,0 +1,20 @@
|
|||
using Moonlight.App.Models.Enums;
|
||||
|
||||
namespace Moonlight.App.Extensions.Attributes;
|
||||
|
||||
public class RequirePermissionAttribute : Attribute
|
||||
{
|
||||
public int PermissionInteger = 0;
|
||||
|
||||
public RequirePermissionAttribute(){}
|
||||
|
||||
public RequirePermissionAttribute(int perms)
|
||||
{
|
||||
PermissionInteger = perms;
|
||||
}
|
||||
|
||||
public RequirePermissionAttribute(Permission permission)
|
||||
{
|
||||
PermissionInteger = (int)permission;
|
||||
}
|
||||
}
|
|
@ -19,10 +19,12 @@
|
|||
<Folder Include="App\Http\Middleware\" />
|
||||
<Folder Include="App\Http\Requests\" />
|
||||
<Folder Include="App\Http\Resources\" />
|
||||
<Folder Include="wwwroot\img\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Ben.Demystifier" Version="0.4.1" />
|
||||
<PackageReference Include="BlazorTable" Version="1.17.0" />
|
||||
<PackageReference Include="JWT" Version="10.1.1" />
|
||||
<PackageReference Include="MailKit" Version="4.2.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.0">
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
<script src="/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="/js/toaster.js"></script>
|
||||
<script src="/js/moonlight.js"></script>
|
||||
<script src="/_content/BlazorTable/BlazorTable.min.js"></script>
|
||||
<script src="/_framework/blazor.server.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,3 +1,4 @@
|
|||
using BlazorTable;
|
||||
using Moonlight.App.Database;
|
||||
using Moonlight.App.Extensions;
|
||||
using Moonlight.App.Helpers;
|
||||
|
@ -55,6 +56,7 @@ builder.Services.AddRazorPages();
|
|||
builder.Services.AddServerSideBlazor();
|
||||
builder.Services.AddHttpContextAccessor();
|
||||
builder.Services.AddControllers();
|
||||
builder.Services.AddBlazorTable();
|
||||
|
||||
builder.Logging.ClearProviders();
|
||||
builder.Logging.AddProvider(new LogMigrateProvider());
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
<div class="card mb-5 mb-xl-10">
|
||||
<div class="card-body pt-0 pb-0">
|
||||
<ul class="nav nav-stretch nav-line-tabs nav-line-tabs-2x border-transparent fs-5 fw-bold">
|
||||
<li class="nav-item mt-2">
|
||||
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 0 ? "active" : "")" href="/admin/users">
|
||||
Overview
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item mt-2">
|
||||
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 1 ? "active" : "")" href="/admin/users/sessions">
|
||||
Sessions
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code
|
||||
{
|
||||
[Parameter]
|
||||
public int Index { get; set; }
|
||||
}
|
50
Moonlight/Shared/Components/Partials/LazyLoader.razor
Normal file
50
Moonlight/Shared/Components/Partials/LazyLoader.razor
Normal file
|
@ -0,0 +1,50 @@
|
|||
@if (loaded)
|
||||
{
|
||||
@ChildContent
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="d-flex justify-content-center py-4">
|
||||
<span class="fs-1 spinner-border spinner-border-lg align-middle me-2"></span>
|
||||
<span class="mt-3 fs-5">@(Text)</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@code
|
||||
{
|
||||
[Parameter]
|
||||
public RenderFragment ChildContent { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<LazyLoader, Task> Load { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string Text { get; set; } = "";
|
||||
|
||||
private bool loaded = false;
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
await Load.Invoke(this);
|
||||
loaded = true;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SetText(string text)
|
||||
{
|
||||
Text = text;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
public async Task Reload()
|
||||
{
|
||||
loaded = false;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
await Load.Invoke(this);
|
||||
loaded = true;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
64
Moonlight/Shared/Components/Partials/PermissionChecker.razor
Normal file
64
Moonlight/Shared/Components/Partials/PermissionChecker.razor
Normal file
|
@ -0,0 +1,64 @@
|
|||
@using Moonlight.App.Extensions.Attributes
|
||||
@using Moonlight.App.Models.Abstractions
|
||||
@using Moonlight.App.Services
|
||||
|
||||
@inject IdentityService IdentityService
|
||||
|
||||
@if (Allowed)
|
||||
{
|
||||
@ChildContent
|
||||
}
|
||||
else
|
||||
{
|
||||
<h1>Ha, nein ;)</h1>
|
||||
}
|
||||
|
||||
@code
|
||||
{
|
||||
[CascadingParameter(Name = "TargetPageType")]
|
||||
public Type? TargetPageType { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public RenderFragment ChildContent { get; set; }
|
||||
|
||||
private bool Allowed = false;
|
||||
|
||||
protected override Task OnParametersSetAsync()
|
||||
{
|
||||
if(TargetPageType == null)
|
||||
return Task.CompletedTask;
|
||||
|
||||
var attributes = TargetPageType.GetCustomAttributes(true);
|
||||
var permAttrs = attributes
|
||||
.Where(x => x.GetType() == typeof(RequirePermissionAttribute))
|
||||
.Select(x => x as RequirePermissionAttribute)
|
||||
.ToArray();
|
||||
|
||||
Allowed = true;
|
||||
|
||||
if (permAttrs.Any())
|
||||
{
|
||||
Allowed = false;
|
||||
|
||||
foreach (var permissionRequired in permAttrs)
|
||||
{
|
||||
if(permissionRequired == null)
|
||||
continue;
|
||||
|
||||
var permission = PermissionStorage.GetFromInteger(permissionRequired.PermissionInteger);
|
||||
|
||||
if (IdentityService.Permissions[permission])
|
||||
{
|
||||
Allowed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!Allowed)
|
||||
{
|
||||
//Logger.Warn($"{IdentityService.Ip} has tried to access {NavigationManager.Uri} without permission", "security");
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
|
@ -1,4 +1,8 @@
|
|||
@using Moonlight.Shared.Layouts
|
||||
@using Moonlight.App.Services
|
||||
@using Moonlight.App.Models.Enums
|
||||
|
||||
@inject IdentityService IdentityService
|
||||
|
||||
<div class="app-sidebar flex-column @(Layout.ShowMobileSidebar ? "drawer drawer-start drawer-on" : "")">
|
||||
<div class="app-sidebar-header d-flex flex-stack d-none d-lg-flex pt-8 pb-2">
|
||||
|
@ -20,6 +24,67 @@
|
|||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="menu-item">
|
||||
<a class="menu-link " href="/store">
|
||||
<span class="menu-icon">
|
||||
<i class="bx bx-sm bx-store"></i>
|
||||
</span>
|
||||
<span class="menu-title">
|
||||
Store
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="menu-item">
|
||||
<a class="menu-link " href="/services">
|
||||
<span class="menu-icon">
|
||||
<i class="bx bx-sm bxs-component"></i>
|
||||
</span>
|
||||
<span class="menu-title">
|
||||
Services
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="menu-item">
|
||||
<a class="menu-link " href="/community">
|
||||
<span class="menu-icon">
|
||||
<i class="bx bx-sm bx-group"></i>
|
||||
</span>
|
||||
<span class="menu-title">
|
||||
Community
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
@if (IdentityService.Permissions[Permission.AdminMenu])
|
||||
{
|
||||
<div class="menu-item mt-5">
|
||||
<div class="menu-heading text-uppercase fs-7 fw-bold">
|
||||
Admin
|
||||
</div>
|
||||
<div class="app-sidebar-separator separator"></div>
|
||||
</div>
|
||||
|
||||
<div class="menu-item">
|
||||
<a class="menu-link " href="/admin">
|
||||
<span class="menu-icon">
|
||||
<i class="bx bx-sm bxs-dashboard"></i>
|
||||
</span>
|
||||
<span class="menu-title">
|
||||
Dashboard
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="menu-item">
|
||||
<a class="menu-link " href="/admin/users">
|
||||
<span class="menu-icon">
|
||||
<i class="bx bx-sm bx-group"></i>
|
||||
</span>
|
||||
<span class="menu-title">
|
||||
Users
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
@using System.Diagnostics
|
||||
@using Moonlight.App.Exceptions
|
||||
@using Moonlight.App.Models.Enums
|
||||
@using Moonlight.App.Services
|
||||
|
||||
@inherits ErrorBoundaryBase
|
||||
@inject IdentityService IdentityService
|
||||
|
||||
@if (Crashed)
|
||||
{
|
||||
if (Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == "Development") // TODO: Add check for admin perms to show exceptions to admins
|
||||
if (Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == "Development" || IdentityService.Permissions[Permission.AdminViewExceptions]) // TODO: Add check for admin perms to show exceptions to admins
|
||||
{
|
||||
if (Exception != null)
|
||||
{
|
||||
|
|
|
@ -36,7 +36,9 @@
|
|||
{
|
||||
<DefaultLayout>
|
||||
<SoftErrorHandler>
|
||||
<PermissionChecker>
|
||||
@Body
|
||||
</PermissionChecker>
|
||||
</SoftErrorHandler>
|
||||
</DefaultLayout>
|
||||
}
|
||||
|
|
6
Moonlight/Shared/Views/Admin/Index.razor
Normal file
6
Moonlight/Shared/Views/Admin/Index.razor
Normal file
|
@ -0,0 +1,6 @@
|
|||
@page "/admin"
|
||||
|
||||
@using Moonlight.App.Extensions.Attributes
|
||||
@using Moonlight.App.Models.Enums
|
||||
|
||||
@attribute [RequirePermission(Permission.AdminOverview)]
|
52
Moonlight/Shared/Views/Admin/Users/Index.razor
Normal file
52
Moonlight/Shared/Views/Admin/Users/Index.razor
Normal file
|
@ -0,0 +1,52 @@
|
|||
@page "/admin/users"
|
||||
|
||||
@using Moonlight.App.Extensions.Attributes
|
||||
@using Moonlight.App.Models.Enums
|
||||
@using Moonlight.App.Repositories
|
||||
@using BlazorTable
|
||||
|
||||
@attribute [RequirePermission(Permission.AdminUsers)]
|
||||
|
||||
@inject Repository<User> UserRepository
|
||||
|
||||
<AdminUsersNavigation Index="0"/>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<LazyLoader Load="Load">
|
||||
<Table TableItem="User"
|
||||
Items="AllUsers"
|
||||
PageSize="50"
|
||||
TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3 fs-6"
|
||||
TableHeadClass="fw-bold text-muted">
|
||||
<Column TableItem="User" Title="Id" Field="@(x => x.Id)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="User" Title="Email" Field="@(x => x.Email)" Sortable="true" Filterable="true">
|
||||
<Template>
|
||||
<a href="/admin/users/view/@(context.Id)">@(context.Email)</a>
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="User" Title="Username" Field="@(x => x.Username)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="User" Title="Created at" Field="@(x => x.CreatedAt)" Sortable="true" Filterable="true">
|
||||
<Template>
|
||||
<span>@(Formatter.FormatDate(context.CreatedAt))</span>
|
||||
</Template>
|
||||
</Column>
|
||||
<Pager ShowPageNumber="true" ShowTotalCount="true" AlwaysShow="true"/>
|
||||
</Table>
|
||||
</LazyLoader>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code
|
||||
{
|
||||
private User[] AllUsers;
|
||||
|
||||
private Task Load(LazyLoader _)
|
||||
{
|
||||
AllUsers = UserRepository
|
||||
.Get()
|
||||
.ToArray();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
61
Moonlight/Shared/Views/Admin/Users/Sessions.razor
Normal file
61
Moonlight/Shared/Views/Admin/Users/Sessions.razor
Normal file
|
@ -0,0 +1,61 @@
|
|||
@page "/admin/users/sessions"
|
||||
|
||||
@using Moonlight.App.Extensions.Attributes
|
||||
@using Moonlight.App.Models.Enums
|
||||
@using BlazorTable
|
||||
@using Moonlight.App.Models.Abstractions
|
||||
@using Moonlight.App.Services
|
||||
|
||||
@attribute [RequirePermission(Permission.AdminSessions)]
|
||||
|
||||
@inject SessionService SessionService
|
||||
|
||||
<AdminUsersNavigation Index="1"/>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<LazyLoader Load="Load">
|
||||
<Table TableItem="Session"
|
||||
Items="SessionService.Sessions"
|
||||
PageSize="50"
|
||||
TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3 fs-6"
|
||||
TableHeadClass="fw-bold text-muted">
|
||||
<Column TableItem="Session" Title="IP" Field="@(x => x.Ip)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="Session" Title="URL" Field="@(x => x.Url)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="Session" Title="User" Field="@(x => x.User)" Sortable="false" Filterable="false">
|
||||
<Template>
|
||||
<span>@(context.User?.Username ?? "Guest")</span>
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="Session" Title="Last activity" Field="@(x => x.UpdatedAt)" Sortable="true" Filterable="true">
|
||||
<Template>
|
||||
<span>@(Formatter.FormatUptime(DateTime.UtcNow - context.UpdatedAt))</span>
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="Session" Title="Connected since" Field="@(x => x.UpdatedAt)" Sortable="true" Filterable="true">
|
||||
<Template>
|
||||
<span>@(Formatter.FormatUptime(DateTime.UtcNow - context.CreatedAt))</span>
|
||||
</Template>
|
||||
</Column>
|
||||
<Pager ShowPageNumber="true" ShowTotalCount="true" AlwaysShow="true"/>
|
||||
</Table>
|
||||
</LazyLoader>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code
|
||||
{
|
||||
private Task Load(LazyLoader _)
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
await InvokeAsync(StateHasChanged);
|
||||
await Task.Delay(TimeSpan.FromSeconds(1));
|
||||
}
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
|
@ -1,20 +1,38 @@
|
|||
@page "/"
|
||||
@using Moonlight.App.Exceptions
|
||||
|
||||
<h1>Hello, world!</h1>
|
||||
@using Moonlight.App.Services
|
||||
|
||||
<ConfirmButton Text="Crash" WorkingText="Crashing" CssClasses="btn-danger" OnClick="Do" />
|
||||
<ConfirmButton Text="Crash" WorkingText="Crashing" CssClasses="btn-danger" OnClick="Do2" />
|
||||
@inject IdentityService IdentityService
|
||||
|
||||
@code
|
||||
{
|
||||
private async Task Do()
|
||||
{
|
||||
throw new DisplayException("LOL");
|
||||
}
|
||||
<div class="card border-0" style="background-image: url('img/covers/minecraft.png'); background-repeat: no-repeat; background-position: bottom; background-size: cover">
|
||||
<div class="card-body">
|
||||
<span class="h2">Welcome back, <span class="text-info">@(IdentityService.CurrentUser.Username)</span></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
private async Task Do2()
|
||||
{
|
||||
throw new FormatException("LOL");
|
||||
}
|
||||
}
|
||||
<div class="mt-5 row">
|
||||
<div class="col-md-3 col-12">
|
||||
<div href="#" class="card card-body text-center fs-6">
|
||||
<div class="mb-5">
|
||||
Do you want to order new services? Are you searching for our service specifications and prices?
|
||||
</div>
|
||||
<a href="/store" class="btn btn-primary">Go to store</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3 col-12">
|
||||
<div href="#" class="card card-body text-center fs-6">
|
||||
<div class="mb-5">
|
||||
Do you want to order new services? Are you searching for our service specifications and prices?
|
||||
</div>
|
||||
<a href="/store" class="btn btn-primary">Go to store</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3 col-12">
|
||||
<div href="#" class="card card-body text-center fs-6">
|
||||
<div class="mb-5">
|
||||
Do you want to order new services? Are you searching for our service specifications and prices?
|
||||
</div>
|
||||
<a href="/store" class="btn btn-primary">Go to store</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -8,3 +8,4 @@
|
|||
@using Moonlight.Shared.Components.Navigations
|
||||
@using Moonlight.App.Services.Interop
|
||||
@using Moonlight.App.Exceptions
|
||||
@using Moonlight.App.Database.Entities
|
BIN
Moonlight/wwwroot/img/covers/minecraft.png
vendored
Normal file
BIN
Moonlight/wwwroot/img/covers/minecraft.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 204 KiB |
Loading…
Reference in a new issue