Ported settings pages to Blazor
This commit is contained in:
parent
9f4cd6c50d
commit
73a9468c37
7 changed files with 177 additions and 304 deletions
|
@ -1,149 +0,0 @@
|
||||||
using LANCommander.Data.Models;
|
|
||||||
using LANCommander.Models;
|
|
||||||
using LANCommander.Services;
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using Microsoft.AspNetCore.Identity;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
|
|
||||||
namespace LANCommander.Controllers
|
|
||||||
{
|
|
||||||
[Authorize(Roles = "Administrator")]
|
|
||||||
public class SettingsController : BaseController
|
|
||||||
{
|
|
||||||
private readonly SettingService SettingService;
|
|
||||||
private readonly UserManager<User> UserManager;
|
|
||||||
|
|
||||||
public SettingsController(SettingService settingService, UserManager<User> userManager)
|
|
||||||
{
|
|
||||||
SettingService = settingService;
|
|
||||||
UserManager = userManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IActionResult Index()
|
|
||||||
{
|
|
||||||
return RedirectToAction(nameof(General));
|
|
||||||
}
|
|
||||||
|
|
||||||
public IActionResult General()
|
|
||||||
{
|
|
||||||
var settings = SettingService.GetSettings();
|
|
||||||
|
|
||||||
return View(settings);
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost]
|
|
||||||
public IActionResult General(LANCommanderSettings settings)
|
|
||||||
{
|
|
||||||
SettingService.SaveSettings(settings);
|
|
||||||
|
|
||||||
return RedirectToAction(nameof(General));
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IActionResult> Users()
|
|
||||||
{
|
|
||||||
var users = new List<UserViewModel>();
|
|
||||||
|
|
||||||
foreach (var user in UserManager.Users)
|
|
||||||
{
|
|
||||||
var savePath = Path.Combine("Save", user.Id.ToString());
|
|
||||||
long saveSize = 0;
|
|
||||||
|
|
||||||
if (Directory.Exists(savePath))
|
|
||||||
saveSize = new DirectoryInfo(savePath).EnumerateFiles().Sum(f => f.Length);
|
|
||||||
|
|
||||||
users.Add(new UserViewModel()
|
|
||||||
{
|
|
||||||
Id = user.Id,
|
|
||||||
UserName = user.UserName,
|
|
||||||
Roles = await UserManager.GetRolesAsync(user),
|
|
||||||
SavesSize = saveSize
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return View(users);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IActionResult> DeleteUser(Guid id)
|
|
||||||
{
|
|
||||||
var user = await UserManager.FindByIdAsync(id.ToString());
|
|
||||||
var admins = await UserManager.GetUsersInRoleAsync("Administrator");
|
|
||||||
|
|
||||||
if (user.UserName == HttpContext.User.Identity.Name)
|
|
||||||
{
|
|
||||||
Alert("You cannot delete yourself!", "danger");
|
|
||||||
|
|
||||||
return RedirectToAction(nameof(Users));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (admins.Count == 1 && admins.First().Id == id)
|
|
||||||
{
|
|
||||||
Alert("You cannot delete the only admin user!", "danger");
|
|
||||||
|
|
||||||
return RedirectToAction(nameof(Users));
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await UserManager.DeleteAsync(user);
|
|
||||||
|
|
||||||
Alert("User successfully deleted!", "success");
|
|
||||||
|
|
||||||
return RedirectToAction(nameof(Users));
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
Alert("User could not be deleted!", "danger");
|
|
||||||
|
|
||||||
return RedirectToAction(nameof(Users));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IActionResult> PromoteUser(Guid id)
|
|
||||||
{
|
|
||||||
var user = await UserManager.FindByIdAsync(id.ToString());
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await UserManager.AddToRoleAsync(user, "Administrator");
|
|
||||||
|
|
||||||
Alert("User promoted to administrator!", "success");
|
|
||||||
|
|
||||||
return RedirectToAction(nameof(Users));
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Alert("User could not be promoted!", "danger");
|
|
||||||
|
|
||||||
return RedirectToAction(nameof(Users));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IActionResult> DemoteUser(Guid id)
|
|
||||||
{
|
|
||||||
var user = await UserManager.FindByIdAsync(id.ToString());
|
|
||||||
var admins = await UserManager.GetUsersInRoleAsync("Administrator");
|
|
||||||
|
|
||||||
if (user.UserName == HttpContext.User.Identity.Name)
|
|
||||||
{
|
|
||||||
Alert("You cannot demote yourself!", "danger");
|
|
||||||
|
|
||||||
return RedirectToAction(nameof(Users));
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await UserManager.RemoveFromRoleAsync(user, "Administrator");
|
|
||||||
|
|
||||||
Alert("User successfully demoted!", "success");
|
|
||||||
|
|
||||||
return RedirectToAction(nameof(Users));
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
Alert("User could not be demoted!", "danger");
|
|
||||||
|
|
||||||
return RedirectToAction(nameof(Users));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
67
LANCommander/Pages/Settings/General.razor
Normal file
67
LANCommander/Pages/Settings/General.razor
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
@page "/Settings/General"
|
||||||
|
@using LANCommander.Models;
|
||||||
|
@layout SettingsLayout
|
||||||
|
@inject SettingService SettingService
|
||||||
|
@inject ISnackbar Snackbar
|
||||||
|
|
||||||
|
<MudCard Elevation="0">
|
||||||
|
<MudCardHeader>
|
||||||
|
<CardHeaderContent>
|
||||||
|
<MudText Typo="Typo.h6">General</MudText>
|
||||||
|
</CardHeaderContent>
|
||||||
|
</MudCardHeader>
|
||||||
|
|
||||||
|
<MudCardContent>
|
||||||
|
<MudText Typo="Typo.subtitle1">IGDB Credentials</MudText>
|
||||||
|
<MudForm Model="@Settings" @ref="Form">
|
||||||
|
<MudTextField @bind-Value="Settings.IGDBClientId" For="@(() => Settings.IGDBClientId)" Immediate="true" Label="Client ID" />
|
||||||
|
<MudTextField @bind-Value="Settings.IGDBClientSecret"
|
||||||
|
For="@(() => Settings.IGDBClientSecret)"
|
||||||
|
Immediate="true"
|
||||||
|
Label="Client Secret"
|
||||||
|
InputType="@IGDBClientSecretInputType"
|
||||||
|
Adornment="Adornment.End"
|
||||||
|
AdornmentIcon="@IGDBClientSecretInputIcon"
|
||||||
|
OnAdornmentClick="() => ToggleClientSecretInput()" />
|
||||||
|
<MudText Typo="Typo.caption">In order to use IGDB metadata, you need a Twitch developer account. <MudLink Href="https://api-docs.igdb.com/#account-creation" Typo="Typo.caption" Target="_blank">Click here</MudLink> for more details.</MudText>
|
||||||
|
</MudForm>
|
||||||
|
</MudCardContent>
|
||||||
|
|
||||||
|
<MudCardActions>
|
||||||
|
<MudButton Variant="Variant.Filled" Color="Color.Primary" StartIcon="@Icons.Material.Filled.Save" OnClick="Save">Save</MudButton>
|
||||||
|
</MudCardActions>
|
||||||
|
</MudCard>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private MudForm Form;
|
||||||
|
private LANCommanderSettings Settings;
|
||||||
|
|
||||||
|
private bool ShowIGDBClientSecret = false;
|
||||||
|
private InputType IGDBClientSecretInputType = InputType.Password;
|
||||||
|
private string IGDBClientSecretInputIcon = Icons.Material.Filled.Visibility;
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
Settings = SettingService.GetSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ToggleClientSecretInput()
|
||||||
|
{
|
||||||
|
ShowIGDBClientSecret = !ShowIGDBClientSecret;
|
||||||
|
IGDBClientSecretInputIcon = ShowIGDBClientSecret ? Icons.Material.Filled.VisibilityOff : Icons.Material.Filled.Visibility;
|
||||||
|
IGDBClientSecretInputType = ShowIGDBClientSecret ? InputType.Text : InputType.Password;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Save()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
SettingService.SaveSettings(Settings);
|
||||||
|
Snackbar.Add("Settings saved!", Severity.Success);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
Snackbar.Add("An unknown error occurred", Severity.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
19
LANCommander/Pages/Settings/SettingsLayout.razor
Normal file
19
LANCommander/Pages/Settings/SettingsLayout.razor
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
@inherits LayoutComponentBase
|
||||||
|
@layout MainLayout
|
||||||
|
|
||||||
|
<MudPaper>
|
||||||
|
<MudGrid Style="width: 100%">
|
||||||
|
<MudItem xs="12" sm="4" md="3" lg="2">
|
||||||
|
<MudNavMenu Bordered="true">
|
||||||
|
<MudText Typo="Typo.h6" Class="px-4">Settings</MudText>
|
||||||
|
<MudDivider Class="my-2" />
|
||||||
|
<MudNavLink Href="/SettingsNew/General" Match="NavLinkMatch.Prefix">General</MudNavLink>
|
||||||
|
<MudNavLink Href="/SettingsNew/Users" Match="NavLinkMatch.Prefix">Users</MudNavLink>
|
||||||
|
</MudNavMenu>
|
||||||
|
</MudItem>
|
||||||
|
|
||||||
|
<MudItem xs="12" sm="8" md="9" lg="10">
|
||||||
|
@Body
|
||||||
|
</MudItem>
|
||||||
|
</MudGrid>
|
||||||
|
</MudPaper>
|
91
LANCommander/Pages/Settings/Users.razor
Normal file
91
LANCommander/Pages/Settings/Users.razor
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
@page "/Settings/Users"
|
||||||
|
@using LANCommander.Models;
|
||||||
|
@layout SettingsLayout
|
||||||
|
@inject UserManager<User> UserManager
|
||||||
|
@inject RoleManager<Role> RoleManager
|
||||||
|
@inject ISnackbar Snackbar
|
||||||
|
|
||||||
|
<MudTable Items="@UserList.Where(u => String.IsNullOrEmpty(Search) || u.UserName.ToLower().Contains(Search.ToLower().Trim()))" RowsPerPage="25" Hover="true" Elevation="0">
|
||||||
|
<ToolBarContent>
|
||||||
|
<MudText Typo="Typo.h6">Users</MudText>
|
||||||
|
<MudSpacer />
|
||||||
|
<MudTextField @bind-Value="Search" Placeholder="Search" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Search" IconSize="Size.Medium" Class="mt-0"></MudTextField>
|
||||||
|
</ToolBarContent>
|
||||||
|
|
||||||
|
<HeaderContent>
|
||||||
|
<MudTh>Username</MudTh>
|
||||||
|
<MudTh>Roles</MudTh>
|
||||||
|
<MudTh>Saves</MudTh>
|
||||||
|
<MudTh></MudTh>
|
||||||
|
</HeaderContent>
|
||||||
|
|
||||||
|
<RowTemplate>
|
||||||
|
<MudTd>@context.UserName</MudTd>
|
||||||
|
<MudTd>@String.Join(", ", context.Roles)</MudTd>
|
||||||
|
<MudTd>@ByteSizeLib.ByteSize.FromBytes(context.SavesSize)</MudTd>
|
||||||
|
<MudTd>
|
||||||
|
@if (!context.Roles.Any(r => r == "Administrator"))
|
||||||
|
{
|
||||||
|
<MudButton OnClick="() => PromoteUser(context)">Promote</MudButton>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<MudButton OnClick="() => DemoteUser(context)">Demote</MudButton>
|
||||||
|
}
|
||||||
|
</MudTd>
|
||||||
|
</RowTemplate>
|
||||||
|
</MudTable>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
private ICollection<UserViewModel> UserList { get; set; }
|
||||||
|
private string Search { get; set; }
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
await RefreshUserList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RefreshUserList()
|
||||||
|
{
|
||||||
|
UserList = new List<UserViewModel>();
|
||||||
|
|
||||||
|
foreach (var user in UserManager.Users)
|
||||||
|
{
|
||||||
|
var savePath = Path.Combine("Save", user.Id.ToString());
|
||||||
|
|
||||||
|
long saveSize = 0;
|
||||||
|
|
||||||
|
if (Directory.Exists(savePath))
|
||||||
|
saveSize = new DirectoryInfo(savePath).EnumerateFiles().Sum(f => f.Length);
|
||||||
|
|
||||||
|
UserList.Add(new UserViewModel()
|
||||||
|
{
|
||||||
|
Id = user.Id,
|
||||||
|
UserName = user.UserName,
|
||||||
|
Roles = await UserManager.GetRolesAsync(user),
|
||||||
|
SavesSize = saveSize
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task PromoteUser(UserViewModel user)
|
||||||
|
{
|
||||||
|
await UserManager.AddToRoleAsync(UserManager.Users.First(u => u.UserName == user.UserName), "Administrator");
|
||||||
|
await RefreshUserList();
|
||||||
|
|
||||||
|
Snackbar.Add($"Promoted {user.UserName}!", Severity.Success);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task DemoteUser(UserViewModel user)
|
||||||
|
{
|
||||||
|
if (UserList.SelectMany(u => u.Roles).Count(r => r == "Administrator") == 1)
|
||||||
|
{
|
||||||
|
Snackbar.Add("Cannot demote the only administrator!", Severity.Error);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await UserManager.RemoveFromRoleAsync(UserManager.Users.First(u => u.UserName == user.UserName), "Administrator");
|
||||||
|
await RefreshUserList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,61 +0,0 @@
|
||||||
@model LANCommander.Models.LANCommanderSettings
|
|
||||||
|
|
||||||
@{
|
|
||||||
ViewData["Title"] = "Settings | Users";
|
|
||||||
}
|
|
||||||
|
|
||||||
<div class="container-xl">
|
|
||||||
<!-- Page title -->
|
|
||||||
<div class="page-header d-print-none">
|
|
||||||
<div class="row align-items-center">
|
|
||||||
<div class="col">
|
|
||||||
<div class="page-pretitle">Settings</div>
|
|
||||||
<h2 class="page-title">
|
|
||||||
General
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="page-body">
|
|
||||||
<div class="container-xl">
|
|
||||||
<div class="card">
|
|
||||||
<div class="row g-0">
|
|
||||||
@{
|
|
||||||
await Html.RenderPartialAsync("_SidebarPartial");
|
|
||||||
}
|
|
||||||
|
|
||||||
<form method="post" class="col d-flex flex-column">
|
|
||||||
<div class="card-body">
|
|
||||||
<h2 class="mb-4">General</h2>
|
|
||||||
<h3 class="card-title mt-4">IGDB Credentials</h3>
|
|
||||||
<p class="card-subtitle">In order to use IGDB metadata, you need a Twitch developer account. <a href="https://api-docs.igdb.com/#account-creation" target="_blank">Click here</a> for more details.</p>
|
|
||||||
<div class="row g-3">
|
|
||||||
<div class="col-md">
|
|
||||||
<label asp-for="IGDBClientId" class="form-label"></label>
|
|
||||||
<input asp-for="IGDBClientId" class="form-control" />
|
|
||||||
</div>
|
|
||||||
<div class="col-md">
|
|
||||||
<label asp-for="IGDBClientSecret" class="form-label"></label>
|
|
||||||
<input asp-for="IGDBClientSecret" class="form-control" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="card-footer bg-transparent mt-auto">
|
|
||||||
<div class="btn-list justify-content-end">
|
|
||||||
<button type="submit" class="btn btn-primary">Save</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@section Scripts {
|
|
||||||
@{
|
|
||||||
await Html.RenderPartialAsync("_ValidationScriptsPartial");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,86 +0,0 @@
|
||||||
@model IEnumerable<LANCommander.Models.UserViewModel>
|
|
||||||
|
|
||||||
@{
|
|
||||||
ViewData["Title"] = "Settings | Users";
|
|
||||||
}
|
|
||||||
|
|
||||||
<div class="container-xl">
|
|
||||||
<!-- Page title -->
|
|
||||||
<div class="page-header d-print-none">
|
|
||||||
<div class="row align-items-center">
|
|
||||||
<div class="col">
|
|
||||||
<div class="page-pretitle">Settings</div>
|
|
||||||
<h2 class="page-title">
|
|
||||||
Users
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="page-body">
|
|
||||||
<div class="container-xl">
|
|
||||||
<div class="card">
|
|
||||||
<div class="row g-0">
|
|
||||||
@{
|
|
||||||
await Html.RenderPartialAsync("_SidebarPartial");
|
|
||||||
}
|
|
||||||
|
|
||||||
<div class="col d-flex flex-column">
|
|
||||||
<div class="card-body">
|
|
||||||
<h2 class="mb-4">Users</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="table-responsive">
|
|
||||||
<table class="table table-vcenter table-mobile-md card-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Username</th>
|
|
||||||
<th>Role</th>
|
|
||||||
<th>Saves</th>
|
|
||||||
<th></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
|
|
||||||
<tbody>
|
|
||||||
@foreach (var item in Model.OrderBy(u => u.UserName))
|
|
||||||
{
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
@item.UserName
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
@String.Join(", ", item.Roles)
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
@ByteSizeLib.ByteSize.FromBytes(item.SavesSize)
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div class="btn-list flex-nowrap justify-content-end">
|
|
||||||
@if (!item.Roles.Any(r => r == "Administrator"))
|
|
||||||
{
|
|
||||||
<a asp-action="PromoteUser" asp-route-id="@item.Id" class="btn btn-ghost-primary">Promote</a>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<a asp-action="DemoteUser" asp-route-id="@item.Id" class="btn btn-ghost-primary">Demote</a>
|
|
||||||
}
|
|
||||||
<a asp-action="DeleteUser" asp-route-id="@item.Id" class="btn btn-danger">Delete</a>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@section Scripts {
|
|
||||||
@{
|
|
||||||
await Html.RenderPartialAsync("_ValidationScriptsPartial");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
<div class="col-3 d-none d-md-block border-end">
|
|
||||||
<div class="card-body">
|
|
||||||
<div class="list-group list-group-transparent">
|
|
||||||
<a asp-action="General" asp-controller="Settings" class="list-group-item list-group-item-action d-flex align-items-center">General</a>
|
|
||||||
<a asp-action="Users" asp-controller="Settings" class="list-group-item list-group-item-action d-flex align-items-center">Users</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
Loading…
Add table
Reference in a new issue