mirror of
https://github.com/CannedNet/CannedNet.git
synced 2026-10-27 07:58:59 -07:00
basic admin panel stuff
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
@page "/setup/accounts"
|
||||
@attribute [Authorize(Policy = "OwnerOnly")]
|
||||
@rendermode InteractiveServer
|
||||
@using CannedNet.Admin.Models
|
||||
@using CannedNet.Admin.Services
|
||||
@inject AdminAuthService AuthService
|
||||
|
||||
<PageTitle>Admin Accounts</PageTitle>
|
||||
|
||||
<h3>Admin Accounts</h3>
|
||||
<p class="text-muted">Manage admin panel accounts. Only the owner can create or delete accounts.</p>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div class="card-header">Create New Admin Account</div>
|
||||
<div class="card-body">
|
||||
<form>
|
||||
<div class="row">
|
||||
<div class="col-md-4 mb-3">
|
||||
<label class="form-label">Username</label>
|
||||
<input type="text" class="form-control" @bind="newUsername" required />
|
||||
</div>
|
||||
<div class="col-md-4 mb-3">
|
||||
<label class="form-label">Password</label>
|
||||
<input type="password" class="form-control" @bind="newPassword" required minlength="6" />
|
||||
</div>
|
||||
<div class="col-md-4 mb-3">
|
||||
<label class="form-label">Confirm Password</label>
|
||||
<input type="password" class="form-control" @bind="newConfirmPassword" required />
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-success" @onclick="CreateAccount" disabled="@creating">
|
||||
@(creating ? "Creating..." : "Create Account")
|
||||
</button>
|
||||
@if (!string.IsNullOrEmpty(createMessage))
|
||||
{
|
||||
<span class="ms-2 @(createMessage.Contains("Error") ? "text-danger" : "text-success")">@createMessage</span>
|
||||
}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">Admin Accounts</div>
|
||||
<div class="card-body">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Username</th>
|
||||
<th>Role</th>
|
||||
<th>Created</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var user in AuthService.GetAll())
|
||||
{
|
||||
<tr>
|
||||
<td>@user.Id</td>
|
||||
<td>@user.Username</td>
|
||||
<td>
|
||||
@if (user.IsOwner)
|
||||
{
|
||||
<td>Owner</td>
|
||||
}
|
||||
else
|
||||
{
|
||||
<td>Admin</td>
|
||||
}
|
||||
</td>
|
||||
<td>@user.CreatedAt.ToString("g")</td>
|
||||
<td>
|
||||
@if (!user.IsOwner)
|
||||
{
|
||||
<button class="btn btn-sm btn-danger" @onclick="() => DeleteUser(user.Id)">Delete</button>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private string newUsername = "";
|
||||
private string newPassword = "";
|
||||
private string newConfirmPassword = "";
|
||||
private bool creating;
|
||||
private string? createMessage;
|
||||
|
||||
private async Task CreateAccount()
|
||||
{
|
||||
creating = true;
|
||||
createMessage = null;
|
||||
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(newUsername))
|
||||
{
|
||||
createMessage = "Username is required.";
|
||||
return;
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(newPassword) || newPassword.Length < 6)
|
||||
{
|
||||
createMessage = "Password must be at least 6 characters.";
|
||||
return;
|
||||
}
|
||||
if (newPassword != newConfirmPassword)
|
||||
{
|
||||
createMessage = "Passwords do not match.";
|
||||
return;
|
||||
}
|
||||
if (AuthService.GetByUsername(newUsername) != null)
|
||||
{
|
||||
createMessage = "Username already exists.";
|
||||
return;
|
||||
}
|
||||
|
||||
AuthService.CreateUser(newUsername, newPassword, isOwner: false);
|
||||
createMessage = "Admin account created successfully!";
|
||||
newUsername = "";
|
||||
newPassword = "";
|
||||
newConfirmPassword = "";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
createMessage = $"Error: {ex.Message}";
|
||||
}
|
||||
finally
|
||||
{
|
||||
creating = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DeleteUser(int id)
|
||||
{
|
||||
var confirmed = await JSRuntime.InvokeAsync<bool>("confirm", "Delete this admin account?");
|
||||
if (!confirmed) return;
|
||||
AuthService.DeleteUser(id);
|
||||
}
|
||||
|
||||
[Inject] private IJSRuntime JSRuntime { get; set; } = default!;
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
@page "/setup"
|
||||
@rendermode InteractiveServer
|
||||
@using CannedNet.Admin.Services
|
||||
@inject AdminAuthService AuthService
|
||||
@inject NavigationManager Navigation
|
||||
|
||||
<PageTitle>First-Time Setup</PageTitle>
|
||||
|
||||
<div class="row justify-content-center mt-5">
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header"><h4 class="mb-0">First-Time Setup</h4></div>
|
||||
<div class="card-body">
|
||||
@if (completed)
|
||||
{
|
||||
<div class="alert alert-success">
|
||||
<h5>Owner account created!</h5>
|
||||
<p>You can now <a href="/login">log in</a> with your new admin account.</p>
|
||||
</div>
|
||||
}
|
||||
else if (AuthService.HasAnyUser())
|
||||
{
|
||||
<div class="alert alert-info">
|
||||
Admin accounts already exist. <a href="/login">Go to login</a>.
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<p class="text-muted">Create the initial owner account. This account will have full access and can create additional admin accounts.</p>
|
||||
<form>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Username</label>
|
||||
<input type="text" class="form-control" @bind="username" required />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Password</label>
|
||||
<input type="password" class="form-control" @bind="password" required minlength="6" />
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Confirm Password</label>
|
||||
<input type="password" class="form-control" @bind="confirmPassword" required />
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary w-100" @onclick="CreateOwner" disabled="@creating">
|
||||
@(creating ? "Creating..." : "Create Owner Account")
|
||||
</button>
|
||||
@if (!string.IsNullOrEmpty(message))
|
||||
{
|
||||
<div class="alert mt-3 @(message.Contains("Error") ? "alert-danger" : "alert-success")">@message</div>
|
||||
}
|
||||
</form>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private string username = "";
|
||||
private string password = "";
|
||||
private string confirmPassword = "";
|
||||
private bool creating;
|
||||
private bool completed;
|
||||
private string? message;
|
||||
|
||||
private async Task CreateOwner()
|
||||
{
|
||||
creating = true;
|
||||
message = null;
|
||||
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(username))
|
||||
{
|
||||
message = "Username is required.";
|
||||
return;
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(password) || password.Length < 6)
|
||||
{
|
||||
message = "Password must be at least 6 characters.";
|
||||
return;
|
||||
}
|
||||
if (password != confirmPassword)
|
||||
{
|
||||
message = "Passwords do not match.";
|
||||
return;
|
||||
}
|
||||
if (AuthService.HasAnyUser())
|
||||
{
|
||||
message = "Admin accounts already exist.";
|
||||
return;
|
||||
}
|
||||
if (AuthService.GetByUsername(username) != null)
|
||||
{
|
||||
message = "Username already taken.";
|
||||
return;
|
||||
}
|
||||
|
||||
AuthService.CreateUser(username, password, isOwner: true);
|
||||
completed = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
message = $"Error: {ex.Message}";
|
||||
}
|
||||
finally
|
||||
{
|
||||
creating = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
@page "/setup/nameserver"
|
||||
@rendermode InteractiveServer
|
||||
@using CannedNet.Admin.Models
|
||||
@using System.Text.Json
|
||||
|
||||
<PageTitle>Nameserver Setup</PageTitle>
|
||||
|
||||
<h3>Nameserver Endpoints</h3>
|
||||
<p class="text-muted">Configure the base server URL the game client connects to. All endpoints are derived from this URL.</p>
|
||||
|
||||
@if (loading)
|
||||
{
|
||||
<div class="spinner-border" role="status"></div>
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(error))
|
||||
{
|
||||
<div class="alert alert-danger">@error</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<form>
|
||||
<div class="mb-3">
|
||||
<button type="submit" class="btn btn-success" @onclick="Save" disabled="@saving">
|
||||
@(saving ? "Saving..." : "Save Changes")
|
||||
</button>
|
||||
@if (!string.IsNullOrEmpty(saveMessage))
|
||||
{
|
||||
<span class="@(saveError ? "text-danger" : "text-success") ms-2">@saveMessage</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label" for="baseUrl">Base URL:</label>
|
||||
<input id="baseUrl" class="form-control font-monospace" @bind="baseUrl" placeholder="https://cannednet.lapis.codes" />
|
||||
<div class="form-text">All endpoint URLs will use this as the base.</div>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Service</th>
|
||||
<th>Full URL</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var (name, path) in EndpointDefinitions.Endpoints)
|
||||
{
|
||||
<tr>
|
||||
<td><code>@name</code></td>
|
||||
<td><code>@(baseUrl.TrimEnd('/') + path)</code></td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</form>
|
||||
}
|
||||
|
||||
@code {
|
||||
[Inject] private HttpClient Http { get; set; } = default!;
|
||||
|
||||
private bool loading = true;
|
||||
private bool saving;
|
||||
private bool saveError;
|
||||
private string? error;
|
||||
private string? saveMessage;
|
||||
private string baseUrl = "";
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var json = await Http.GetStringAsync("/admin/api/endpoints");
|
||||
baseUrl = EndpointDefinitions.ExtractBaseUrl(json);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
error = $"Failed to load: {ex.Message}";
|
||||
}
|
||||
finally
|
||||
{
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task Save()
|
||||
{
|
||||
saving = true;
|
||||
saveMessage = null;
|
||||
saveError = false;
|
||||
try
|
||||
{
|
||||
var json = EndpointDefinitions.BuildEndpointsJson(baseUrl);
|
||||
var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json");
|
||||
var response = await Http.PutAsync("/admin/api/endpoints", content);
|
||||
response.EnsureSuccessStatusCode();
|
||||
saveMessage = "Saved successfully!";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
saveMessage = $"Error: {ex.Message}";
|
||||
saveError = true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
saving = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user