Added user view/edit. Added some tool tips. Improved some ui

This commit is contained in:
Marcel Baumgartner
2023-10-29 16:05:29 +01:00
parent c3acb4898e
commit 0241be13cf
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"> <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"> <ul class="nav nav-stretch nav-line-tabs nav-line-tabs-2x border-transparent fs-5 fw-bold">
<li class="nav-item mt-2"> <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"> <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> </a>
</li> </li>
<li class="nav-item mt-2"> <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"> <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> </a>
</li> </li>
</ul> </ul>

View File

@@ -6,10 +6,12 @@ else
{ {
if (ShowAsCard) if (ShowAsCard)
{ {
<div class="card card-body"> <div class="d-flex flex-column flex-center">
<div class="d-flex justify-content-center py-4"> <div class="card card-body">
<span class="fs-1 spinner-border spinner-border-lg align-middle me-2"></span> <div class="d-flex justify-content-center py-4">
<span class="mt-3 fs-5">@(Text)</span> <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>
</div> </div>
} }
@@ -17,7 +19,7 @@ else
{ {
<div class="d-flex justify-content-center py-4"> <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="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> </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)] @attribute [RequirePermission(Permission.AdminCommunity)]
<AdminCommunityNavigation Index="1" /> <AdminCommunityNavigation Index="1"/>
<div class="card card-body border-primary fs-5 mt-5"> <Tooltip>
To protect from trollers and toxic people you can configure words using 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. regex expressions to block automatically to ensure no one can write bad things in the community tab.
</div> </Tooltip>
<div class="mt-5"> <div class="mt-5">
<AutoCrud TItem="WordFilter" <AutoCrud TItem="WordFilter"
@@ -22,8 +22,8 @@
TUpdateForm="EditWordFilter" TUpdateForm="EditWordFilter"
Title="Manage word filter" Title="Manage word filter"
Load="LoadData"> Load="LoadData">
<Column TableItem="WordFilter" Field="@(x => x.Id)" Title="Id" 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" /> <Column TableItem="WordFilter" Field="@(x => x.Filter)" Title="Filter" Sortable="false" Filterable="true"/>
</AutoCrud> </AutoCrud>
</div> </div>

View File

@@ -11,7 +11,7 @@
<AdminUsersNavigation Index="0"/> <AdminUsersNavigation Index="0"/>
<div class="card"> <div class="card mt-5">
<div class="card-body"> <div class="card-body">
<LazyLoader Load="Load"> <LazyLoader Load="Load">
<Table TableItem="User" <Table TableItem="User"
@@ -20,12 +20,12 @@
TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3 fs-6" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3 fs-6"
TableHeadClass="fw-bold text-muted"> TableHeadClass="fw-bold text-muted">
<Column TableItem="User" Title="Id" Field="@(x => x.Id)" Sortable="true" Filterable="true"/> <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> <Template>
<a href="/admin/users/view/@(context.Id)">@(context.Email)</a> <a href="/admin/users/view/@(context.Id)">@(context.Username)</a>
</Template> </Template>
</Column> </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"> <Column TableItem="User" Title="Created at" Field="@(x => x.CreatedAt)" Sortable="true" Filterable="true">
<Template> <Template>
<span>@(Formatter.FormatDate(context.CreatedAt))</span> <span>@(Formatter.FormatDate(context.CreatedAt))</span>

View File

@@ -12,7 +12,11 @@
<AdminUsersNavigation Index="1"/> <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"> <div class="card-body">
<LazyLoader Load="Load"> <LazyLoader Load="Load">
<Table TableItem="Session" <Table TableItem="Session"
@@ -21,10 +25,21 @@
TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3 fs-6" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3 fs-6"
TableHeadClass="fw-bold text-muted"> TableHeadClass="fw-bold text-muted">
<Column TableItem="Session" Title="IP" Field="@(x => x.Ip)" Sortable="true" Filterable="true"/> <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"> <Column TableItem="Session" Title="User" Field="@(x => x.User)" Sortable="false" Filterable="false">
<Template> <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> </Template>
</Column> </Column>
<Column TableItem="Session" Title="Last activity" Field="@(x => x.UpdatedAt)" Sortable="true" Filterable="true"> <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.Routing
@using Microsoft.AspNetCore.Components.Web @using Microsoft.AspNetCore.Components.Web
@using Microsoft.JSInterop @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.App.Helpers
@using Moonlight.Shared.Components.Partials @using Moonlight.Shared.Components.Partials
@using Moonlight.Shared.Components.Forms @using Moonlight.Shared.Components.Forms
@using Moonlight.Shared.Components.Navigations @using Moonlight.Shared.Components.Navigations
@using Moonlight.App.Services.Interop @using Moonlight.Shared.Components.Alerts
@using Moonlight.App.Exceptions
@using Moonlight.App.Database.Entities

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