Compare commits
7
Commits
v1.0.2
..
fbafa54322
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fbafa54322 | ||
|
|
9607746912 | ||
|
|
785d33e289 | ||
|
|
a3eeb8b6d1 | ||
|
|
cae61116c8 | ||
|
|
ced0f61b87 | ||
|
|
9751c81574 |
@@ -97,6 +97,16 @@ public class AuthController(
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether user registration is currently enabled.
|
||||
/// </summary>
|
||||
/// <returns>200 if enabled, 403 if disabled.</returns>
|
||||
[HttpGet("register")]
|
||||
public ActionResult CheckRegistrationEnabled() {
|
||||
var regSetting = settingsRepository.Get(Settings.UserRegistrationEnabled.AsString());
|
||||
return regSetting?.Value == "true" ? Ok() : StatusCode(403);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a new user account.
|
||||
/// </summary>
|
||||
|
||||
@@ -158,9 +158,19 @@ public class JobScheduler(IServiceProvider serviceProvider, ILogger<JobScheduler
|
||||
logger?.LogWarning("A file system crawl job is already in progress, cannot queue another at this moment.");
|
||||
return;
|
||||
}
|
||||
// if the scheduled scan list isn't populated, fetch active folders directly from DB
|
||||
var targetFolders = folders;
|
||||
if (targetFolders == null || targetFolders.Count == 0) {
|
||||
using var scope = serviceProvider.CreateScope();
|
||||
var folderRepo = scope.ServiceProvider.GetRequiredService<IFolderRepository>();
|
||||
targetFolders = folderRepo.GetAll().Where(f => f.Active).ToList();
|
||||
}
|
||||
// queue a crawl job for each active folder
|
||||
folders?.ForEach(f => { jobManager.EnqueueJob(jobManager.CreateJob<FileSystemCrawlJob>(f.Id, f.BasePath)); });
|
||||
logger?.LogInformation("Queued file system crawl jobs.");
|
||||
targetFolders.ForEach(f => { jobManager.EnqueueJob(jobManager.CreateJob<FileSystemCrawlJob>(f.Id, f.BasePath)); });
|
||||
if (targetFolders.Count > 0)
|
||||
logger?.LogInformation("Queued {Count} file system crawl job(s).", targetFolders.Count);
|
||||
else
|
||||
logger?.LogWarning("No active folders to scan.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -131,10 +131,10 @@
|
||||
<JobTree Jobs="_filteredSorted" FetchChildren="FetchChildren" EmptyText="" PollInterval="pollInterval" />
|
||||
@if (pastJobTotal > pastJobPageSize) {
|
||||
<div class="d-flex align-items-center justify-content-center gap-2 py-1 border-top">
|
||||
<button class="btn btn-sm btn-secondary" disabled="@(pastJobPage <= 1)" @onclick="() => LoadPastJobs(pastJobPage - 1)">
|
||||
<button class="btn btn-sm btn-secondary" disabled="@(pastJobPage <= 0)" @onclick="() => LoadPastJobs(pastJobPage - 1)">
|
||||
<i class="bi bi-chevron-left"></i> Prev
|
||||
</button>
|
||||
<span class="small text-muted">@pastJobPage / @pastJobTotalPages</span>
|
||||
<span class="small text-muted">@(pastJobPage + 1) / @pastJobTotalPages</span>
|
||||
<button class="btn btn-sm btn-secondary" disabled="@(pastJobPage >= pastJobTotalPages)" @onclick="() => LoadPastJobs(pastJobPage + 1)">
|
||||
Next <i class="bi bi-chevron-right"></i>
|
||||
</button>
|
||||
@@ -155,7 +155,7 @@
|
||||
|
||||
List<JobStatusDto> activeRoots = [];
|
||||
List<JobStatusDto> pastJobRoots = [];
|
||||
int pastJobPage = 1;
|
||||
int pastJobPage = 0;
|
||||
int pastJobPageSize = 15;
|
||||
int pastJobTotal = 0;
|
||||
int pastJobTotalPages => Math.Max(1, (int)Math.Ceiling((double)pastJobTotal / pastJobPageSize));
|
||||
@@ -213,11 +213,11 @@
|
||||
return;
|
||||
}
|
||||
LoginService.LoggedUserChanged += async (_, _) => {
|
||||
await RefreshActiveRoots();
|
||||
await LoadPastJobs(1);
|
||||
await RefreshActiveRoots();
|
||||
await LoadPastJobs(0);
|
||||
};
|
||||
await RefreshActiveRoots();
|
||||
await LoadPastJobs(1);
|
||||
await LoadPastJobs(0);
|
||||
StartPolling();
|
||||
}
|
||||
|
||||
@@ -232,7 +232,7 @@
|
||||
StartPolling();
|
||||
} else {
|
||||
StopPolling();
|
||||
await LoadPastJobs(1);
|
||||
await LoadPastJobs(0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -310,7 +310,7 @@
|
||||
await JobsService.StartJob(jobType);
|
||||
errorMessage = null;
|
||||
await RefreshActiveRoots();
|
||||
if (pastJobPage != 1) pastJobPage = 1;
|
||||
if (pastJobPage != 0) pastJobPage = 0;
|
||||
await LoadPastJobs(pastJobPage);
|
||||
} catch (HttpRequestException ex) when ((int?)ex.StatusCode == 501) {
|
||||
errorMessage = $"{jobType} is not yet implemented on the server.";
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
@using System.Net
|
||||
@page "/Register"
|
||||
|
||||
@inject LoginService loginService
|
||||
@@ -5,30 +6,39 @@
|
||||
|
||||
<PageTitle>Register</PageTitle>
|
||||
|
||||
<div class="d-flex flex-column mx-sm-auto mx-3">
|
||||
@if (!string.IsNullOrEmpty(_error)) {
|
||||
<div class="m-2 p-1 border-danger border-2 rounded-3 bg-danger-subtle">
|
||||
<div class="text-danger-emphasis m-2">@_error</div>
|
||||
</div>
|
||||
}
|
||||
<form class="d-flex flex-column align-items-center">
|
||||
<input type="text" class="form-control my-1" placeholder="Username" required="required" id="username"
|
||||
@bind="_username" @bind:event="oninput"/>
|
||||
<input type="email" class="form-control my-1" placeholder="Email" required="required" id="email"
|
||||
@bind="_email" @bind:event="oninput"/>
|
||||
@if (_checkingStatus) {
|
||||
<LoadSpinner/>
|
||||
} else if (_registrationDisabled) {
|
||||
<EmptyState Variant="warning" Title="Registration Closed">
|
||||
<p>New user registration is currently disabled on this server.</p>
|
||||
<a href="/login" class="btn btn-primary">Go to Login</a>
|
||||
</EmptyState>
|
||||
} else {
|
||||
<div class="d-flex flex-column mx-sm-auto mx-3">
|
||||
@if (!string.IsNullOrEmpty(_error)) {
|
||||
<div class="m-2 p-1 border-danger border-2 rounded-3 bg-danger-subtle">
|
||||
<div class="text-danger-emphasis m-2">@_error</div>
|
||||
</div>
|
||||
}
|
||||
<form class="d-flex flex-column align-items-center">
|
||||
<input type="text" class="form-control my-1" placeholder="Username" required="required" id="username"
|
||||
@bind="_username" @bind:event="oninput"/>
|
||||
<input type="email" class="form-control my-1" placeholder="Email" required="required" id="email"
|
||||
@bind="_email" @bind:event="oninput"/>
|
||||
|
||||
<PasswordField @ref="_passwordField"
|
||||
@bind-Password="_password"
|
||||
@bind-ConfirmPassword="_confirmPassword" />
|
||||
<PasswordField @ref="_passwordField"
|
||||
@bind-Password="_password"
|
||||
@bind-ConfirmPassword="_confirmPassword" />
|
||||
|
||||
<div class="d-flex flex-row justify-content-around w-100 mt-2">
|
||||
<button class="btn btn-primary m-2" type="button" id="register-button"
|
||||
disabled="@(!CanSubmit)" @onclick="Register_OnClick">Register</button>
|
||||
<button class="btn btn-primary m-2" type="button" id="login-button"
|
||||
@onclick="Login_OnClick">Login</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="d-flex flex-row justify-content-around w-100 mt-2">
|
||||
<button class="btn btn-primary m-2" type="button" id="register-button"
|
||||
disabled="@(!CanSubmit)" @onclick="Register_OnClick">Register</button>
|
||||
<button class="btn btn-primary m-2" type="button" id="login-button"
|
||||
@onclick="Login_OnClick">Login</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
}
|
||||
|
||||
@code {
|
||||
PasswordField? _passwordField;
|
||||
@@ -37,18 +47,29 @@
|
||||
string _password = string.Empty;
|
||||
string _confirmPassword = string.Empty;
|
||||
string _error = string.Empty;
|
||||
bool _checkingStatus = true;
|
||||
bool _registrationDisabled;
|
||||
|
||||
bool CanSubmit => _passwordField?.IsValid == true
|
||||
&& !string.IsNullOrWhiteSpace(_username)
|
||||
&& !string.IsNullOrWhiteSpace(_email);
|
||||
|
||||
protected override async Task OnInitializedAsync() {
|
||||
_registrationDisabled = !await loginService.IsRegistrationEnabledAsync();
|
||||
_checkingStatus = false;
|
||||
}
|
||||
|
||||
void Login_OnClick() => navigation.NavigateTo("login");
|
||||
|
||||
async Task Register_OnClick() {
|
||||
_error = string.Empty;
|
||||
var ok = await loginService.Register(_username, _email, _password);
|
||||
if (ok) {
|
||||
var status = await loginService.Register(_username, _email, _password);
|
||||
if (status == HttpStatusCode.OK) {
|
||||
navigation.NavigateTo("login");
|
||||
} else if (status == HttpStatusCode.Forbidden) {
|
||||
_error = "Registration is currently disabled.";
|
||||
} else if (status == HttpStatusCode.Conflict) {
|
||||
_error = "Username or email already taken.";
|
||||
} else {
|
||||
_error = "Registration failed. Please try again.";
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Net;
|
||||
using Blazored.LocalStorage;
|
||||
using Butter.Dtos;
|
||||
using Butter.Dtos.User;
|
||||
@@ -265,8 +266,8 @@ public sealed class LoginService : ServiceBase {
|
||||
/// <param name="username">The desired username.</param>
|
||||
/// <param name="email">The email address.</param>
|
||||
/// <param name="password">The password.</param>
|
||||
/// <returns>True if registration succeeded.</returns>
|
||||
public async Task<bool> Register(string username, string email, string password) {
|
||||
/// <returns>HttpStatusCode indicating the result (200 = success, 403 = disabled, 409 = conflict).</returns>
|
||||
public async Task<HttpStatusCode> Register(string username, string email, string password) {
|
||||
logger.LogInformation("Attempting to register user with username: {Username}", username);
|
||||
|
||||
var registerDto = new UserRegisterDto() {
|
||||
@@ -277,7 +278,7 @@ public sealed class LoginService : ServiceBase {
|
||||
|
||||
var response = await Client.PostAsJsonAsync("api/auth/register", registerDto);
|
||||
logger.LogInformation("Result: {ResponseStatusCode}", response.StatusCode);
|
||||
return response.IsSuccessStatusCode;
|
||||
return response.StatusCode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -321,5 +322,18 @@ public sealed class LoginService : ServiceBase {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether user registration is currently enabled on the server.
|
||||
/// </summary>
|
||||
/// <returns>True if registration is allowed.</returns>
|
||||
public async Task<bool> IsRegistrationEnabledAsync() {
|
||||
try {
|
||||
var response = await Client.GetAsync("api/auth/register");
|
||||
return response.StatusCode == HttpStatusCode.OK;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -329,12 +329,13 @@ $btn-theme-colors: (
|
||||
}
|
||||
|
||||
// ── Inter font overrides ──
|
||||
html, body {
|
||||
html {
|
||||
overflow-y: scroll;
|
||||
font-family: 'Inter', 'Helvetica Neue', Helvetica, Arial, sans-serif;
|
||||
min-height: 100dvh;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: inherit;
|
||||
min-height: 100dvh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: radial-gradient(ellipse at 50% 0%,
|
||||
|
||||
@@ -206,7 +206,7 @@ window.profileCropper = {
|
||||
s.square.style.height = sidePx + 'px';
|
||||
|
||||
// Handles at square corners
|
||||
var hs = 14;
|
||||
var hs = 18;
|
||||
var corners = [
|
||||
{ left: -hs / 2, top: -hs / 2 },
|
||||
{ left: sidePx - hs / 2, top: -hs / 2 },
|
||||
|
||||
Reference in New Issue
Block a user