Implemented permissions. Added user overview and session overview. Added lazy loader.. Did some ui
This commit is contained in:
@@ -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\Middleware\" />
|
||||||
<Folder Include="App\Http\Requests\" />
|
<Folder Include="App\Http\Requests\" />
|
||||||
<Folder Include="App\Http\Resources\" />
|
<Folder Include="App\Http\Resources\" />
|
||||||
|
<Folder Include="wwwroot\img\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Ben.Demystifier" Version="0.4.1" />
|
<PackageReference Include="Ben.Demystifier" Version="0.4.1" />
|
||||||
|
<PackageReference Include="BlazorTable" Version="1.17.0" />
|
||||||
<PackageReference Include="JWT" Version="10.1.1" />
|
<PackageReference Include="JWT" Version="10.1.1" />
|
||||||
<PackageReference Include="MailKit" Version="4.2.0" />
|
<PackageReference Include="MailKit" Version="4.2.0" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="7.0.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/bootstrap.bundle.min.js"></script>
|
||||||
<script src="/js/toaster.js"></script>
|
<script src="/js/toaster.js"></script>
|
||||||
<script src="/js/moonlight.js"></script>
|
<script src="/js/moonlight.js"></script>
|
||||||
|
<script src="/_content/BlazorTable/BlazorTable.min.js"></script>
|
||||||
<script src="/_framework/blazor.server.js"></script>
|
<script src="/_framework/blazor.server.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using BlazorTable;
|
||||||
using Moonlight.App.Database;
|
using Moonlight.App.Database;
|
||||||
using Moonlight.App.Extensions;
|
using Moonlight.App.Extensions;
|
||||||
using Moonlight.App.Helpers;
|
using Moonlight.App.Helpers;
|
||||||
@@ -55,6 +56,7 @@ builder.Services.AddRazorPages();
|
|||||||
builder.Services.AddServerSideBlazor();
|
builder.Services.AddServerSideBlazor();
|
||||||
builder.Services.AddHttpContextAccessor();
|
builder.Services.AddHttpContextAccessor();
|
||||||
builder.Services.AddControllers();
|
builder.Services.AddControllers();
|
||||||
|
builder.Services.AddBlazorTable();
|
||||||
|
|
||||||
builder.Logging.ClearProviders();
|
builder.Logging.ClearProviders();
|
||||||
builder.Logging.AddProvider(new LogMigrateProvider());
|
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.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 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">
|
<div class="app-sidebar-header d-flex flex-stack d-none d-lg-flex pt-8 pb-2">
|
||||||
@@ -20,6 +24,67 @@
|
|||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
@using System.Diagnostics
|
@using System.Diagnostics
|
||||||
@using Moonlight.App.Exceptions
|
@using Moonlight.App.Exceptions
|
||||||
|
@using Moonlight.App.Models.Enums
|
||||||
|
@using Moonlight.App.Services
|
||||||
|
|
||||||
@inherits ErrorBoundaryBase
|
@inherits ErrorBoundaryBase
|
||||||
|
@inject IdentityService IdentityService
|
||||||
|
|
||||||
@if (Crashed)
|
@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)
|
if (Exception != null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -36,7 +36,9 @@
|
|||||||
{
|
{
|
||||||
<DefaultLayout>
|
<DefaultLayout>
|
||||||
<SoftErrorHandler>
|
<SoftErrorHandler>
|
||||||
|
<PermissionChecker>
|
||||||
@Body
|
@Body
|
||||||
|
</PermissionChecker>
|
||||||
</SoftErrorHandler>
|
</SoftErrorHandler>
|
||||||
</DefaultLayout>
|
</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 "/"
|
@page "/"
|
||||||
@using Moonlight.App.Exceptions
|
|
||||||
|
|
||||||
<h1>Hello, world!</h1>
|
@using Moonlight.App.Services
|
||||||
|
|
||||||
<ConfirmButton Text="Crash" WorkingText="Crashing" CssClasses="btn-danger" OnClick="Do" />
|
@inject IdentityService IdentityService
|
||||||
<ConfirmButton Text="Crash" WorkingText="Crashing" CssClasses="btn-danger" OnClick="Do2" />
|
|
||||||
|
|
||||||
@code
|
<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">
|
||||||
private async Task Do()
|
<span class="h2">Welcome back, <span class="text-info">@(IdentityService.CurrentUser.Username)</span></span>
|
||||||
{
|
</div>
|
||||||
throw new DisplayException("LOL");
|
</div>
|
||||||
}
|
|
||||||
|
|
||||||
private async Task Do2()
|
<div class="mt-5 row">
|
||||||
{
|
<div class="col-md-3 col-12">
|
||||||
throw new FormatException("LOL");
|
<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.Shared.Components.Navigations
|
||||||
@using Moonlight.App.Services.Interop
|
@using Moonlight.App.Services.Interop
|
||||||
@using Moonlight.App.Exceptions
|
@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 |
Reference in New Issue
Block a user