Files
ChronoCCG/Chrono/DatabaseTest/Components/Pages/Home.razor
T
6d486f49 1d6207fbfe API Test
2026-08-17 00:45:08 -04:00

346 lines
14 KiB
Plaintext

@page "/"
@implements IDisposable
<PageTitle>Chrono DB Hub - Dashboard</PageTitle>
<div class="d-flex align-items-center justify-content-between mb-4">
<div>
<h2 class="fw-bold mb-1 text-white">Database Test Dashboard</h2>
<p class="text-secondary mb-0">Monitor, manage, and validate all database features referenced in Chrono
Cloud</p>
</div>
<div class="d-flex gap-2">
<button class="btn btn-chrono-secondary" @onclick="RefreshAsync" disabled="@isLoading">
<i class="bi bi-arrow-clockwise @(isLoading ? "spin" : "")"></i> Refresh Status
</button>
<a href="test-suite" class="btn btn-chrono-primary">
<i class="bi bi-play-circle-fill"></i> Run All Tests
</a>
</div>
</div>
@if (!string.IsNullOrEmpty(alertMessage))
{
<div class="alert @(alertIsSuccess ? "alert-success" : "alert-danger") alert-dismissible fade show" role="alert">
<i class="bi @(alertIsSuccess ? "bi-check-circle-fill" : "bi-exclamation-triangle-fill") me-2"></i>
@alertMessage
<button type="button" class="btn-close" @onclick="() => alertMessage = null"></button>
</div>
}
<!-- Database Health & Connection Banner -->
@if (health != null)
{
@if (!health.IsConnected)
{
<div class="chrono-card border-danger mb-4" style="background-color: rgba(248, 81, 73, 0.08);">
<div class="d-flex align-items-start justify-content-between">
<div class="d-flex gap-3">
<i class="bi bi-exclamation-octagon-fill text-danger fs-2"></i>
<div>
<h5 class="text-danger fw-bold mb-1">PostgreSQL Connection Offline</h5>
<p class="text-secondary small mb-2">
@(health.ErrorMessage ?? "Could not connect to the database server. Ensure PostgreSQL is running on the configured host/port.")
</p>
<div class="d-flex gap-2 mt-2">
<button class="btn btn-sm btn-chrono-primary" @onclick="SwitchToInMemoryAsync">
<i class="bi bi-lightning-charge-fill"></i> Switch to In-Memory DB (Instant Testing)
</button>
<a href="diagnostics" class="btn btn-sm btn-chrono-secondary">
<i class="bi bi-gear-fill"></i> Configure Connection
</a>
<button class="btn btn-sm btn-outline-secondary" @onclick="RefreshAsync">
<i class="bi bi-arrow-repeat"></i> Retry Connection
</button>
</div>
</div>
</div>
</div>
</div>
}
else if (health.ProviderType == DatabaseProviderType.InMemory)
{
<div class="chrono-card border-info mb-4" style="background-color: rgba(57, 197, 207, 0.08);">
<div class="d-flex align-items-center justify-content-between">
<div class="d-flex align-items-center gap-3">
<i class="bi bi-cpu-fill text-info fs-2"></i>
<div>
<h6 class="text-info fw-bold mb-0">In-Memory Database Active (@health.ConnectionString)</h6>
<span class="text-secondary small">Running zero-config in-memory tests. Full CRUD and test suites are active.</span>
</div>
</div>
<button class="btn btn-sm btn-chrono-secondary" @onclick="SwitchToPostgresAsync">
<i class="bi bi-database"></i> Switch to PostgreSQL
</button>
</div>
</div>
}
}
<!-- Metrics Row -->
<div class="row g-3 mb-4">
<div class="col-md">
<a href="card-notes" class="text-decoration-none">
<div class="metric-card">
<div class="metric-icon-box metric-icon-blue">
<i class="bi bi-card-text"></i>
</div>
<div>
<div
class="metric-value">@(health?.IsConnected == true ? health.CardNotesCount.ToString() : "-")</div>
<div class="metric-label">Card Notes</div>
</div>
</div>
</a>
</div>
<div class="col-md">
<a href="users" class="text-decoration-none">
<div class="metric-card">
<div class="metric-icon-box metric-icon-yellow">
<i class="bi bi-people-fill"></i>
</div>
<div>
<div class="metric-value">@(health?.IsConnected == true ? health.UsersCount.ToString() : "-")</div>
<div class="metric-label">Users</div>
</div>
</div>
</a>
</div>
<div class="col-md">
<a href="user-decks" class="text-decoration-none">
<div class="metric-card">
<div class="metric-icon-box metric-icon-green">
<i class="bi bi-collection-play"></i>
</div>
<div>
<div
class="metric-value">@(health?.IsConnected == true ? health.UserDecksCount.ToString() : "-")</div>
<div class="metric-label">User Decks</div>
</div>
</div>
</a>
</div>
<div class="col-md">
<a href="passkeys" class="text-decoration-none">
<div class="metric-card">
<div class="metric-icon-box metric-icon-purple">
<i class="bi bi-key-fill"></i>
</div>
<div>
<div
class="metric-value">@(health?.IsConnected == true ? health.PasskeysCount.ToString() : "-")</div>
<div class="metric-label">Passkeys</div>
</div>
</div>
</a>
</div>
<div class="col-md">
<a href="diagnostics" class="text-decoration-none">
<div class="metric-card">
<div class="metric-icon-box metric-icon-yellow">
<i class="bi bi-arrow-left-right"></i>
</div>
<div>
<div class="metric-value">
@(health?.ProviderType == DatabaseProviderType.PostgreSQL
? $"{health.AppliedMigrationsCount} / {health.AppliedMigrationsCount + health.PendingMigrationsCount}"
: "N/A")
</div>
<div class="metric-label">Migrations</div>
</div>
</div>
</a>
</div>
</div>
<!-- Quick Actions & Features -->
<div class="row g-4">
<div class="col-lg-7">
<div class="chrono-card h-100">
<div class="chrono-card-header">
<h5 class="chrono-card-title">
<i class="bi bi-stars text-primary"></i>
Cloud Database Features Under Test
</h5>
<span class="badge bg-secondary">Cloud.AppDbContext</span>
</div>
<div class="d-flex flex-column gap-3">
<div class="p-3 rounded bg-dark border border-secondary border-opacity-25">
<div class="d-flex align-items-center justify-content-between mb-1">
<span class="fw-bold text-white"><i class="bi bi-card-text text-primary me-2"></i> Card Notes Entity (`CardNote`)</span>
<a href="card-notes" class="btn btn-sm btn-chrono-secondary">Manage Notes</a>
</div>
<p class="text-secondary small mb-0">
Tracks developer and user strategic card notes with primary key indexing on
<code>CardName</code>. Supports live search, adding, updating, and deletion.
</p>
</div>
<div class="p-3 rounded bg-dark border border-secondary border-opacity-25">
<div class="d-flex align-items-center justify-content-between mb-1">
<span class="fw-bold text-white"><i class="bi bi-collection-play text-success me-2"></i> User Decks & JSONB (`UserDeck`)</span>
<a href="user-decks" class="btn btn-sm btn-chrono-secondary">Manage Decks</a>
</div>
<p class="text-secondary small mb-0">
Stores full player decks with native PostgreSQL <code>jsonb</code> mapping for
<code>Cards</code> list (with counts) and <code>Divers</code> list.
</p>
</div>
<div class="p-3 rounded bg-dark border border-secondary border-opacity-25">
<div class="d-flex align-items-center justify-content-between mb-1">
<span class="fw-bold text-white"><i class="bi bi-key-fill text-purple me-2"></i> Passkey Credentials (`PasskeyCredential`)</span>
<a href="passkeys" class="btn btn-sm btn-chrono-secondary">Manage Passkeys</a>
</div>
<p class="text-secondary small mb-0">
Stores WebAuthn / FIDO2 binary credentials (raw <code>byte[]</code> for CredentialId, PublicKey,
UserHandle) with counter verification.
</p>
</div>
</div>
</div>
</div>
<div class="col-lg-5">
<div class="chrono-card h-100">
<div class="chrono-card-header">
<h5 class="chrono-card-title">
<i class="bi bi-wrench-adjustable text-warning"></i>
Quick Action Hub
</h5>
</div>
<div class="d-flex flex-column gap-2">
<button class="btn btn-chrono-primary py-2 text-start d-flex align-items-center justify-content-between"
@onclick="SeedDataAsync" disabled="@isActionBusy">
<span><i class="bi bi-box-arrow-in-down me-2"></i> Seed Realistic Sample Data</span>
<i class="bi bi-arrow-right"></i>
</button>
<button
class="btn btn-chrono-secondary py-2 text-start d-flex align-items-center justify-content-between"
@onclick="ApplyMigrationsAsync" disabled="@isActionBusy">
<span><i class="bi bi-database-check me-2"></i> Apply Pending Migrations</span>
<i class="bi bi-arrow-right"></i>
</button>
@if (ConfigService.ProviderType == DatabaseProviderType.PostgreSQL)
{
<button
class="btn btn-chrono-secondary py-2 text-start d-flex align-items-center justify-content-between"
@onclick="SwitchToInMemoryAsync">
<span><i class="bi bi-cpu me-2"></i> Switch to In-Memory DB</span>
<span class="badge bg-purple">Instant</span>
</button>
}
else
{
<button
class="btn btn-chrono-secondary py-2 text-start d-flex align-items-center justify-content-between"
@onclick="SwitchToPostgresAsync">
<span><i class="bi bi-database me-2"></i> Switch to PostgreSQL</span>
<span class="badge bg-primary">Live</span>
</button>
}
<a href="test-suite"
class="btn btn-chrono-outline-primary py-2 text-start d-flex align-items-center justify-content-between">
<span><i class="bi bi-clipboard2-check me-2"></i> Open Test Suite Runner</span>
<i class="bi bi-arrow-right"></i>
</a>
<button
class="btn btn-chrono-danger py-2 text-start d-flex align-items-center justify-content-between mt-2"
@onclick="ResetDatabaseAsync" disabled="@isActionBusy">
<span><i class="bi bi-trash3-fill me-2"></i> Wipe & Reset Database</span>
<i class="bi bi-exclamation-triangle"></i>
</button>
</div>
</div>
</div>
</div>
@code {
private DatabaseHealthStatus? health;
private bool isLoading;
private bool isActionBusy;
private string? alertMessage;
private bool alertIsSuccess;
protected override async Task OnInitializedAsync()
{
ConfigService.OnConfigurationChanged += HandleConfigChanged;
await RefreshAsync();
}
private async Task RefreshAsync()
{
isLoading = true;
health = await DbService.CheckHealthAsync();
isLoading = false;
}
private void HandleConfigChanged()
{
_ = RefreshAsync();
}
private async Task SwitchToInMemoryAsync()
{
ConfigService.SetProvider(DatabaseProviderType.InMemory);
var result = await DbService.EnsureDatabaseCreatedAsync();
alertIsSuccess = result.Success;
alertMessage = "Switched to In-Memory Test Database. " + result.Message;
await RefreshAsync();
}
private async Task SwitchToPostgresAsync()
{
ConfigService.SetProvider(DatabaseProviderType.PostgreSQL);
alertIsSuccess = true;
alertMessage = "Switched provider to PostgreSQL.";
await RefreshAsync();
}
private async Task SeedDataAsync()
{
isActionBusy = true;
var result = await DbService.SeedSampleDataAsync();
isActionBusy = false;
alertIsSuccess = result.Success;
alertMessage = result.Message;
await RefreshAsync();
}
private async Task ApplyMigrationsAsync()
{
isActionBusy = true;
var result = await DbService.ApplyMigrationsAsync();
isActionBusy = false;
alertIsSuccess = result.Success;
alertMessage = result.Message;
await RefreshAsync();
}
private async Task ResetDatabaseAsync()
{
isActionBusy = true;
var result = await DbService.ResetDatabaseAsync();
isActionBusy = false;
alertIsSuccess = result.Success;
alertMessage = result.Message;
await RefreshAsync();
}
public void Dispose()
{
ConfigService.OnConfigurationChanged -= HandleConfigChanged;
}
}