Merge pull request #331 from Moonlight-Panel/AddUserAdmin

Added user view/edit. Added some tool tips. Improved some ui
This commit is contained in:
Marcel Baumgartner 2023-10-29 16:07:14 +01:00 committed by GitHub
commit 52d39151da
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 324 additions and 25 deletions

View file

@ -0,0 +1,33 @@
<div class="d-flex flex-column flex-center text-center p-10">
<div class="card card-flush w-lg-650px py-5">
<div class="card-body py-15 py-lg-20">
<div class="mb-5">
<img src="/svg/nodata.svg" style="width: 10vh" alt="Not found illustration">
</div>
<h1 class="fw-bolder fs-2hx text-gray-900 mb-4">
The requested resource was not found
</h1>
<div class="fw-semibold fs-6 text-gray-500 mb-7">
<span class="fs-5">We were not able to find the requested resource. This can have following reasons</span>
<div class="mt-4 d-flex flex-column align-items-start">
<li class="d-flex align-items-center py-2">
<span class="bullet me-5"></span> The resource was deleted
</li>
<li class="d-flex align-items-center py-2">
<span class="bullet me-5"></span> You have no permission to access this resource
</li>
<li class="d-flex align-items-center py-2">
<span class="bullet me-5"></span> You may have entered invalid data
</li>
<li class="d-flex align-items-center py-2">
<span class="bullet me-5"></span> A unknown bug occured
</li>
<li class="d-flex align-items-center py-2">
<span class="bullet me-5"></span> An api was offline and not proper handled
</li>
</div>
</div>
</div>
</div>
</div>

View file

@ -1,14 +1,14 @@
<div class="card mb-5 mb-xl-10">
<div class="card">
<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
<i class="bx bx-sm bx-group me-2"></i> 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
<i class="bx bx-sm bx-devices me-2"></i> Sessions
</a>
</li>
</ul>

View file

@ -6,10 +6,12 @@ else
{
if (ShowAsCard)
{
<div class="card card-body">
<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 class="d-flex flex-column flex-center">
<div class="card card-body">
<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="ms-3 fs-5 align-middle">@(Text)</span>
</div>
</div>
</div>
}
@ -17,7 +19,7 @@ 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>
<span class="ms-3 fs-5 align-middle">@(Text)</span>
</div>
}
}

View file

@ -0,0 +1,9 @@
<div class="card card-body border-primary fs-5 mt-5">
@ChildContent
</div>
@code
{
[Parameter]
public RenderFragment ChildContent { get; set; }
}

View file

@ -9,12 +9,12 @@
@attribute [RequirePermission(Permission.AdminCommunity)]
<AdminCommunityNavigation Index="1" />
<AdminCommunityNavigation Index="1"/>
<div class="card card-body border-primary fs-5 mt-5">
To protect from trollers and toxic people you can configure words using
<Tooltip>
To protect from trollers and toxic people you can configure words using
regex expressions to block automatically to ensure no one can write bad things in the community tab.
</div>
</Tooltip>
<div class="mt-5">
<AutoCrud TItem="WordFilter"
@ -22,8 +22,8 @@
TUpdateForm="EditWordFilter"
Title="Manage word filter"
Load="LoadData">
<Column TableItem="WordFilter" Field="@(x => x.Id)" Title="Id" Sortable="false" Filterable="true" />
<Column TableItem="WordFilter" Field="@(x => x.Filter)" Title="Filter" Sortable="false" Filterable="true" />
<Column TableItem="WordFilter" Field="@(x => x.Id)" Title="Id" Sortable="false" Filterable="true"/>
<Column TableItem="WordFilter" Field="@(x => x.Filter)" Title="Filter" Sortable="false" Filterable="true"/>
</AutoCrud>
</div>

View file

@ -11,7 +11,7 @@
<AdminUsersNavigation Index="0"/>
<div class="card">
<div class="card mt-5">
<div class="card-body">
<LazyLoader Load="Load">
<Table TableItem="User"
@ -20,12 +20,12 @@
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">
<Column TableItem="User" Title="Username" Field="@(x => x.Username)" Sortable="true" Filterable="true">
<Template>
<a href="/admin/users/view/@(context.Id)">@(context.Email)</a>
<a href="/admin/users/view/@(context.Id)">@(context.Username)</a>
</Template>
</Column>
<Column TableItem="User" Title="Username" Field="@(x => x.Username)" Sortable="true" Filterable="true"/>
<Column TableItem="User" Title="Email" Field="@(x => x.Email)" 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>

View file

@ -12,7 +12,11 @@
<AdminUsersNavigation Index="1"/>
<div class="card">
<Tooltip>
This list shows you every user connected to this moonlight instance. Its updated in realtime
</Tooltip>
<div class="card mt-5">
<div class="card-body">
<LazyLoader Load="Load">
<Table TableItem="Session"
@ -21,10 +25,21 @@
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="URL" Field="@(x => x.Url)" Sortable="true" Filterable="true">
<Template>
<a target="_blank" href="@(context.Url)">@(context.Url)</a>
</Template>
</Column>
<Column TableItem="Session" Title="User" Field="@(x => x.User)" Sortable="false" Filterable="false">
<Template>
<span>@(context.User?.Username ?? "Guest")</span>
@if (context.User == null)
{
<span>Guest</span>
}
else
{
<a href="/admin/users/view/@(context.User.Id)">@(context.User.Username)</a>
}
</Template>
</Column>
<Column TableItem="Session" Title="Last activity" Field="@(x => x.UpdatedAt)" Sortable="true" Filterable="true">

View file

@ -0,0 +1,237 @@
@page "/admin/users/view/{Id:int}"
@using Moonlight.App.Extensions.Attributes
@using Moonlight.App.Models.Enums
@using Moonlight.App.Repositories
@using Microsoft.EntityFrameworkCore
@using Moonlight.App.Database.Entities.Store
@using Moonlight.App.Models.Forms.Admin.Users
@using Moonlight.App.Services
@using Mappy.Net
@using BlazorTable
@using Moonlight.App.Services.Users
@attribute [RequirePermission(Permission.AdminUsers)]
@inject Repository<User> UserRepository
@inject Repository<Service> ServiceRepository
@inject SessionService SessionService
@inject UserService UserService
@inject ToastService ToastService
@inject ConfigService ConfigService
@inject NavigationManager Navigation
<LazyLoader Load="Load" ShowAsCard="true">
@if (User == null)
{
<NotFoundAlert/>
}
else
{
<div class="row">
<div class="col-md-4 col-12">
<div class="card mb-3">
<div class="card-header">
<h3 class="card-title">User details</h3>
</div>
<SmartForm Model="UserForm" OnValidSubmit="UpdateUser">
<div class="card-body">
<div class="form-group mb-3">
<label class="form-label">
Username
</label>
<input @bind="UserForm.Username" type="text" class="form-control form-control-solid" placeholder="Enter a new username">
</div>
<div class="form-group">
<label class="form-label">
Email
</label>
<input @bind="UserForm.Email" type="text" class="form-control form-control-solid" placeholder="Enter a new email address">
</div>
</div>
<div class="card-footer">
<div class="text-end">
<button type="submit" class="btn btn-primary">Save changes</button>
</div>
</div>
</SmartForm>
</div>
<div class="card mb-3">
<SmartForm Model="PasswordForm" OnValidSubmit="UpdatePassword">
<div class="card-body">
<div class="form-group mb-3">
<label class="form-label">
New password
</label>
<input @bind="PasswordForm.Password" type="password" class="form-control form-control-solid" placeholder="Enter a new password">
</div>
</div>
<div class="card-footer">
<div class="text-end">
<button type="submit" class="btn btn-primary">Save changes</button>
</div>
</div>
</SmartForm>
</div>
<div class="card mb-3">
<div class="card-body text-end">
<ConfirmButton OnClick="Delete" Text="Delete user" CssClasses="btn-danger" />
</div>
</div>
</div>
<div class="col-md-4 col-12">
<div class="card mb-3">
<div class="card-body fs-6">
<div class="mb-5">
<div class="fw-bold">Online status</div>
@if (Online)
{
<div class="text-success">Online</div>
}
else
{
<div class="text-danger">Offline</div>
}
</div>
<div class="mb-5">
<div class="fw-bold">Registered at</div>
<div class="text-gray-600">
@Formatter.FormatDate(User.CreatedAt)
</div>
</div>
</div>
</div>
<div class="card mb-3">
<div class="card-header">
<h3 class="card-title">Transactions</h3>
</div>
<div class="card-body">
<Table TableItem="Transaction"
Items="Transactions"
PageSize="3"
TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3 fs-6"
TableHeadClass="fw-bold text-muted">
<Column TableItem="Transaction" Title="" Field="@(x => x.Id)" Sortable="false" Filterable="false" Width="10%">
<Template>
@if (context.Price == 0)
{
<i class="bx bx-sm bx-circle text-info align-middle"></i>
}
else if (context.Price < 0)
{
<i class="bx bx-sm bx-minus text-danger align-middle"></i>
}
else
{
<i class="bx bx-sm bx-plus text-success align-middle"></i>
}
</Template>
</Column>
<Column TableItem="Transaction" Title="" Field="@(x => x.Id)" Sortable="false" Filterable="false">
<Template>
<span>@(ConfigService.Get().Store.Currency) @(Math.Abs(context.Price))</span>
</Template>
</Column>
<Column TableItem="Transaction" Title="" Field="@(x => x.Text)" Sortable="false" Filterable="false"/>
<Pager AlwaysShow="true"/>
</Table>
</div>
</div>
</div>
<div class="col-md-4 col-12">
<div class="card mb-3">
<div class="card-header">
<h3 class="card-title">Services</h3>
</div>
<div class="card-body">
<Table TableItem="Service"
Items="Services"
PageSize="10"
TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3 fs-6"
TableHeadClass="fw-bold text-muted">
<Column TableItem="Service" Title="Id" Field="@(x => x.Id)" Sortable="false" Filterable="false"/>
<Column TableItem="Service" Title="Name" Field="@(x => x.Id)" Sortable="false" Filterable="false">
<Template>
<a href="/service/@(context.Id)">@(context.Nickname ?? $"Service {context.Id}")</a>
</Template>
</Column>
<Column TableItem="Service" Title="Product" Field="@(x => x.Id)" Sortable="false" Filterable="false">
<Template>
<span>@(context.Product.Name)</span>
</Template>
</Column>
<Pager AlwaysShow="true"/>
</Table>
</div>
</div>
</div>
</div>
}
</LazyLoader>
@code
{
[Parameter]
public int Id { get; set; }
private User? User;
private bool Online;
private Service[] Services;
private Transaction[] Transactions;
private UpdateUserForm UserForm;
private UpdateUserPasswordForm PasswordForm = new();
private async Task Load(LazyLoader lazyLoader) // We use the text feature here because users have a lot of data related to them
{
await lazyLoader.SetText("Loading user");
User = UserRepository
.Get()
.Include(x => x.Transactions)
.Include(x => x.CouponUses)
.Include(x => x.GiftCodeUses)
.FirstOrDefault(x => x.Id == Id);
if (User != null)
{
UserForm = Mapper.Map<UpdateUserForm>(User);
await lazyLoader.SetText("Checking online status");
Online = SessionService
.GetSessions()
.Where(x => x.User != null)
.Any(x => x.User!.Id == User.Id);
await lazyLoader.SetText("Loading user services");
Services = ServiceRepository
.Get()
.Include(x => x.Product)
.Where(x => x.Owner.Id == User.Id)
.ToArray();
await lazyLoader.SetText("Sorting transactions");
Transactions = User.Transactions
.OrderByDescending(x => x.Id)
.ToArray();
}
}
private async Task UpdateUser()
{
await UserService.Update(User!, UserForm.Username, UserForm.Email);
await ToastService.Success("Successfully updated user");
}
private async Task UpdatePassword()
{
await UserService.Auth.ChangePassword(User!, PasswordForm.Password);
await ToastService.Success("Successfully updated user password");
}
private async Task Delete()
{
await UserService.Delete(User!);
await ToastService.Success("Successfully deleted user");
Navigation.NavigateTo("/admin/users");
}
}

View file

@ -1,11 +1,13 @@
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.JSInterop
@using Moonlight
@using Moonlight.App.Services.Interop
@using Moonlight.App.Exceptions
@using Moonlight.App.Database.Entities
@using Moonlight.App.Helpers
@using Moonlight.Shared.Components.Partials
@using Moonlight.Shared.Components.Forms
@using Moonlight.Shared.Components.Navigations
@using Moonlight.App.Services.Interop
@using Moonlight.App.Exceptions
@using Moonlight.App.Database.Entities
@using Moonlight.Shared.Components.Alerts

1
Moonlight/wwwroot/svg/nodata.svg vendored Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" data-name="Layer 1" width="647.63626" height="632.17383" viewBox="0 0 647.63626 632.17383" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M687.3279,276.08691H512.81813a15.01828,15.01828,0,0,0-15,15v387.85l-2,.61005-42.81006,13.11a8.00676,8.00676,0,0,1-9.98974-5.31L315.678,271.39691a8.00313,8.00313,0,0,1,5.31006-9.99l65.97022-20.2,191.25-58.54,65.96972-20.2a7.98927,7.98927,0,0,1,9.99024,5.3l32.5498,106.32Z" transform="translate(-276.18187 -133.91309)" fill="#f2f2f2"/><path d="M725.408,274.08691l-39.23-128.14a16.99368,16.99368,0,0,0-21.23-11.28l-92.75,28.39L380.95827,221.60693l-92.75,28.4a17.0152,17.0152,0,0,0-11.28028,21.23l134.08008,437.93a17.02661,17.02661,0,0,0,16.26026,12.03,16.78926,16.78926,0,0,0,4.96972-.75l63.58008-19.46,2-.62v-2.09l-2,.61-64.16992,19.65a15.01489,15.01489,0,0,1-18.73-9.95l-134.06983-437.94a14.97935,14.97935,0,0,1,9.94971-18.73l92.75-28.4,191.24024-58.54,92.75-28.4a15.15551,15.15551,0,0,1,4.40966-.66,15.01461,15.01461,0,0,1,14.32032,10.61l39.0498,127.56.62012,2h2.08008Z" transform="translate(-276.18187 -133.91309)" fill="#3f3d56"/><path d="M398.86279,261.73389a9.0157,9.0157,0,0,1-8.61133-6.3667l-12.88037-42.07178a8.99884,8.99884,0,0,1,5.9712-11.24023l175.939-53.86377a9.00867,9.00867,0,0,1,11.24072,5.9707l12.88037,42.07227a9.01029,9.01029,0,0,1-5.9707,11.24072L401.49219,261.33887A8.976,8.976,0,0,1,398.86279,261.73389Z" transform="translate(-276.18187 -133.91309)" fill="#6c63ff"/><circle cx="190.15351" cy="24.95465" r="20" fill="#6c63ff"/><circle cx="190.15351" cy="24.95465" r="12.66462" fill="#fff"/><path d="M878.81836,716.08691h-338a8.50981,8.50981,0,0,1-8.5-8.5v-405a8.50951,8.50951,0,0,1,8.5-8.5h338a8.50982,8.50982,0,0,1,8.5,8.5v405A8.51013,8.51013,0,0,1,878.81836,716.08691Z" transform="translate(-276.18187 -133.91309)" fill="#e6e6e6"/><path d="M723.31813,274.08691h-210.5a17.02411,17.02411,0,0,0-17,17v407.8l2-.61v-407.19a15.01828,15.01828,0,0,1,15-15H723.93825Zm183.5,0h-394a17.02411,17.02411,0,0,0-17,17v458a17.0241,17.0241,0,0,0,17,17h394a17.0241,17.0241,0,0,0,17-17v-458A17.02411,17.02411,0,0,0,906.81813,274.08691Zm15,475a15.01828,15.01828,0,0,1-15,15h-394a15.01828,15.01828,0,0,1-15-15v-458a15.01828,15.01828,0,0,1,15-15h394a15.01828,15.01828,0,0,1,15,15Z" transform="translate(-276.18187 -133.91309)" fill="#3f3d56"/><path d="M801.81836,318.08691h-184a9.01015,9.01015,0,0,1-9-9v-44a9.01016,9.01016,0,0,1,9-9h184a9.01016,9.01016,0,0,1,9,9v44A9.01015,9.01015,0,0,1,801.81836,318.08691Z" transform="translate(-276.18187 -133.91309)" fill="#6c63ff"/><circle cx="433.63626" cy="105.17383" r="20" fill="#6c63ff"/><circle cx="433.63626" cy="105.17383" r="12.18187" fill="#fff"/></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB