< Summary

Line coverage
100%
Covered lines: 9
Uncovered lines: 0
Coverable lines: 9
Total lines: 162
Line coverage: 100%
Branch coverage
100%
Covered branches: 4
Total branches: 4
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
File 1: BuildRenderTree(...)100%44100%
File 2: .ctor()100%11100%
File 2: .cctor()100%11100%

File(s)

/home/runner/work/chronicis/chronicis/src/Chronicis.Client/Components/Admin/AdminWorldsPanel.razor

#LineLine coverage
 1@using Chronicis.Shared.DTOs
 2
 3<div class="awp-container">
 4    <MudText Typo="Typo.h5" Class="awp-section-title mb-2">World Management</MudText>
 5    <MudText Typo="Typo.body2" Class="mud-text-secondary mb-4">
 6        All worlds across the platform. Deletion is permanent and cannot be undone.
 7    </MudText>
 8
 69    @if (_isLoading)
 10    {
 11        <MudProgressLinear Indeterminate="true" Color="Color.Primary" Class="mb-4" />
 12    }
 513    else if (!string.IsNullOrEmpty(_loadError))
 14    {
 15        <MudAlert Severity="Severity.Error" Class="mb-4">@_loadError</MudAlert>
 16    }
 17    else
 18    {
 19        <MudTable Items="_worlds"
 20                  Dense="true"
 21                  Hover="true"
 22                  Striped="true"
 23                  Loading="_isLoading"
 24                  SortLabel="Sort By"
 25                  Class="awp-table">
 26            <ToolBarContent>
 27                <MudText Typo="Typo.subtitle1">@_worlds.Count world(s)</MudText>
 28                <MudSpacer />
 29                <MudTooltip Text="Refresh">
 30                    <MudIconButton Icon="@Icons.Material.Filled.Refresh"
 31                                   Color="Color.Default"
 32                                   OnClick="LoadWorldsAsync" />
 33                </MudTooltip>
 34            </ToolBarContent>
 35            <HeaderContent>
 36                <MudTh><MudTableSortLabel SortBy="new Func<AdminWorldSummaryDto, object>(x => x.Name)">Name</MudTableSor
 37                <MudTh><MudTableSortLabel SortBy="new Func<AdminWorldSummaryDto, object>(x => x.OwnerName)">Owner</MudTa
 38                <MudTh>Campaigns</MudTh>
 39                <MudTh>Arcs</MudTh>
 40                <MudTh>Articles</MudTh>
 41                <MudTh><MudTableSortLabel SortBy="new Func<AdminWorldSummaryDto, object>(x => x.CreatedAt)">Created</Mud
 42                <MudTh></MudTh>
 43            </HeaderContent>
 44            <RowTemplate>
 45                <MudTd DataLabel="Name">
 46                    <MudText Typo="Typo.body2" Style="font-weight:600;">@context.Name</MudText>
 47                </MudTd>
 48                <MudTd DataLabel="Owner">
 49                    <MudText Typo="Typo.body2">@context.OwnerName</MudText>
 50                    <MudText Typo="Typo.caption" Class="mud-text-secondary">@context.OwnerEmail</MudText>
 51                </MudTd>
 52                <MudTd DataLabel="Campaigns">@context.CampaignCount</MudTd>
 53                <MudTd DataLabel="Arcs">@context.ArcCount</MudTd>
 54                <MudTd DataLabel="Articles">@context.ArticleCount</MudTd>
 55                <MudTd DataLabel="Created">@context.CreatedAt.ToString("yyyy-MM-dd")</MudTd>
 56                <MudTd>
 57                    <MudTooltip Text="Delete world">
 58                        <MudIconButton Icon="@Icons.Material.Filled.DeleteForever"
 59                                       Color="Color.Error"
 60                                       Size="Size.Small"
 61                                       OnClick="@(() => OpenDeleteDialogAsync(context))" />
 62                    </MudTooltip>
 63                </MudTd>
 64            </RowTemplate>
 65            <NoRecordsContent>
 66                <MudText Class="pa-4 mud-text-secondary">No worlds found.</MudText>
 67            </NoRecordsContent>
 68        </MudTable>
 69    }
 70</div>

/home/runner/work/chronicis/chronicis/src/Chronicis.Client/Components/Admin/AdminWorldsPanel.razor.cs

#LineLine coverage
 1using Chronicis.Client.Services;
 2using Chronicis.Shared.DTOs;
 3using Microsoft.AspNetCore.Components;
 4using MudBlazor;
 5
 6namespace Chronicis.Client.Components.Admin;
 7
 8/// <summary>
 9/// Admin panel for listing and deleting worlds across the entire platform.
 10/// Requires the caller (Utilities page) to have already verified sysadmin status.
 11/// </summary>
 12public partial class AdminWorldsPanel : ComponentBase
 13{
 14    [Inject] private IAdminApiService AdminApi { get; set; } = default!;
 15    [Inject] private IDialogService DialogService { get; set; } = default!;
 16    [Inject] private ISnackbar Snackbar { get; set; } = default!;
 17    [Inject] private ILogger<AdminWorldsPanel> Logger { get; set; } = default!;
 18
 619    private List<AdminWorldSummaryDto> _worlds = new();
 20    private bool _isLoading;
 21    private string? _loadError;
 22
 123    private static readonly DialogOptions _dialogOptions = new()
 124    {
 125        CloseOnEscapeKey = true,
 126        MaxWidth = MaxWidth.Small,
 127        FullWidth = true,
 128    };
 29
 30    protected override async Task OnInitializedAsync()
 31        => await LoadWorldsAsync();
 32
 33    internal async Task LoadWorldsAsync()
 34    {
 35        _isLoading = true;
 36        _loadError = null;
 37        StateHasChanged();
 38
 39        try
 40        {
 41            _worlds = await AdminApi.GetWorldSummariesAsync();
 42        }
 43        catch (Exception ex)
 44        {
 45            _loadError = "Failed to load worlds.";
 46            Logger.LogError(ex, "Error loading admin world summaries");
 47        }
 48        finally
 49        {
 50            _isLoading = false;
 51        }
 52    }
 53
 54    internal async Task OpenDeleteDialogAsync(AdminWorldSummaryDto world)
 55    {
 56        var parameters = new DialogParameters<DeleteWorldDialog>
 57        {
 58            { x => x.WorldName, world.Name }
 59        };
 60
 61        var dialog = await DialogService.ShowAsync<DeleteWorldDialog>(
 62            $"Delete \"{world.Name}\"", parameters, _dialogOptions);
 63
 64        var result = await dialog.Result;
 65        if (result is { Canceled: false })
 66        {
 67            await ExecuteDeleteAsync(world);
 68        }
 69    }
 70
 71    private async Task ExecuteDeleteAsync(AdminWorldSummaryDto world)
 72    {
 73        try
 74        {
 75            var success = await AdminApi.DeleteWorldAsync(world.Id);
 76            if (success)
 77            {
 78                Snackbar.Add($"World \"{world.Name}\" deleted.", Severity.Success);
 79                await LoadWorldsAsync();
 80            }
 81            else
 82            {
 83                Snackbar.Add("Delete failed — world may have already been removed.", Severity.Warning);
 84            }
 85        }
 86        catch (Exception ex)
 87        {
 88            Snackbar.Add("An unexpected error occurred during deletion.", Severity.Error);
 89            Logger.LogError(ex, "Error deleting world {WorldId}", world.Id);
 90        }
 91    }
 92}