Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions Procession.AdminWeb/Controllers/DashboardController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// <copyright file="DashboardController.cs" company="Michael Silver">
// Copyright (c) Michael Silver. All rights reserved.
// </copyright>

using Microsoft.AspNetCore.Mvc;
using Procession.AdminWeb.Models;
using Procession.AdminWeb.Services;
using Procession.Common;

namespace Procession.AdminWeb.Controllers;

/// <summary>
/// Controller for the dashboard page showing system statistics.
/// </summary>
public class DashboardController : Controller
{
private readonly AdminService adminService;

/// <summary>
/// Initializes a new instance of the <see cref="DashboardController"/> class.
/// </summary>
/// <param name="adminService">The admin service.</param>
public DashboardController(AdminService adminService)
{
this.adminService = adminService;
}

/// <summary>
/// Shows the dashboard with system statistics and configuration.
/// </summary>
/// <returns>Dashboard view.</returns>
public async Task<IActionResult> Index()
{
var model = new DashboardViewModel
{
Queues = await this.adminService.GetAllQueuesAsync(),
MessageStatistics = await this.adminService.GetMessageStatisticsAsync(),
DatabaseType = this.adminService.GetDatabaseType(),
RecentMessages = await this.adminService.GetMessagesAsync(1, 20), // Get 20 most recent messages
TotalMessageCount = await this.adminService.GetMessageCountAsync(),
ActiveMessageCount = await this.adminService.GetMessageCountAsync(processedOnly: false),
ProcessedMessageCount = await this.adminService.GetMessageCountAsync(processedOnly: true)
};

return this.View(model);
}

/// <summary>
/// Shows error page.
/// </summary>
/// <returns>Error view.</returns>
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return this.View(new ErrorViewModel { RequestId = System.Diagnostics.Activity.Current?.Id ?? this.HttpContext.TraceIdentifier });
}
}
77 changes: 77 additions & 0 deletions Procession.AdminWeb/Controllers/MessagesController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// <copyright file="MessagesController.cs" company="Michael Silver">
// Copyright (c) Michael Silver. All rights reserved.
// </copyright>

using Microsoft.AspNetCore.Mvc;
using Procession.AdminWeb.Models;
using Procession.AdminWeb.Services;

namespace Procession.AdminWeb.Controllers;

/// <summary>
/// Controller for message management and viewing.
/// </summary>
public class MessagesController : Controller
{
private readonly AdminService adminService;

/// <summary>
/// Initializes a new instance of the <see cref="MessagesController"/> class.
/// </summary>
/// <param name="adminService">The admin service.</param>
public MessagesController(AdminService adminService)
{
this.adminService = adminService;
}

/// <summary>
/// Shows paginated list of messages with filtering options.
/// </summary>
/// <param name="page">Page number (1-based).</param>
/// <param name="queueId">Optional queue ID filter.</param>
/// <param name="processedOnly">Optional processed state filter.</param>
/// <returns>Message list view.</returns>
public async Task<IActionResult> Index(int page = 1, long? queueId = null, bool? processedOnly = null)
{
const int pageSize = 50;

var model = new MessageListViewModel
{
CurrentPage = page,
PageSize = pageSize,
SelectedQueueId = queueId,
ProcessedOnly = processedOnly,
Messages = await this.adminService.GetMessagesAsync(page, pageSize, queueId, processedOnly),
TotalCount = await this.adminService.GetMessageCountAsync(queueId, processedOnly),
AvailableQueues = await this.adminService.GetAllQueuesAsync()
};

return this.View(model);
}

/// <summary>
/// Shows detailed information for a specific message.
/// </summary>
/// <param name="id">The message ID.</param>
/// <param name="showPayload">Whether to show the payload content.</param>
/// <returns>Message detail view.</returns>
public async Task<IActionResult> Details(long id, bool showPayload = false)
{
var message = await this.adminService.GetMessageByIdAsync(id);
if (message == null)
{
return this.NotFound();
}

var queue = await this.adminService.GetQueueByIdAsync(message.QueueId);

var model = new MessageDetailViewModel
{
Message = message,
Queue = queue,
ShowPayload = showPayload
};

return this.View(model);
}
}
50 changes: 50 additions & 0 deletions Procession.AdminWeb/Models/DashboardViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// <copyright file="DashboardViewModel.cs" company="Michael Silver">
// Copyright (c) Michael Silver. All rights reserved.
// </copyright>

using Procession.Common;
using Procession.Common.Models;
using Procession.Server.Common.Models;

namespace Procession.AdminWeb.Models;

/// <summary>
/// View model for the dashboard page containing statistics and configuration info.
/// </summary>
public class DashboardViewModel
{
/// <summary>
/// Gets or sets the list of all queues.
/// </summary>
public List<QueueInfo> Queues { get; set; } = new List<QueueInfo>();

/// <summary>
/// Gets or sets message statistics by state.
/// </summary>
public Dictionary<MessageState, int> MessageStatistics { get; set; } = new Dictionary<MessageState, int>();

/// <summary>
/// Gets or sets the database type being used.
/// </summary>
public string DatabaseType { get; set; } = string.Empty;

/// <summary>
/// Gets or sets recent messages for the summary list.
/// </summary>
public List<Message> RecentMessages { get; set; } = new List<Message>();

/// <summary>
/// Gets or sets the total message count.
/// </summary>
public int TotalMessageCount { get; set; }

/// <summary>
/// Gets or sets the active message count (not processed).
/// </summary>
public int ActiveMessageCount { get; set; }

/// <summary>
/// Gets or sets the processed message count.
/// </summary>
public int ProcessedMessageCount { get; set; }
}
21 changes: 21 additions & 0 deletions Procession.AdminWeb/Models/ErrorViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// <copyright file="ErrorViewModel.cs" company="Michael Silver">
// Copyright (c) Michael Silver. All rights reserved.
// </copyright>

namespace Procession.AdminWeb.Models;

/// <summary>
/// View model for error page.
/// </summary>
public class ErrorViewModel
{
/// <summary>
/// Gets or sets the request ID.
/// </summary>
public string? RequestId { get; set; }

/// <summary>
/// Gets a value indicating whether to show the request ID.
/// </summary>
public bool ShowRequestId => !string.IsNullOrEmpty(this.RequestId);
}
71 changes: 71 additions & 0 deletions Procession.AdminWeb/Models/MessageDetailViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// <copyright file="MessageDetailViewModel.cs" company="Michael Silver">
// Copyright (c) Michael Silver. All rights reserved.
// </copyright>

using Procession.Common.Models;
using Procession.Server.Common.Models;

namespace Procession.AdminWeb.Models;

/// <summary>
/// View model for the message detail page showing full message information.
/// </summary>
public class MessageDetailViewModel
{
/// <summary>
/// Gets or sets the message details.
/// </summary>
public Message? Message { get; set; }

/// <summary>
/// Gets or sets the queue information.
/// </summary>
public QueueInfo? Queue { get; set; }

/// <summary>
/// Gets or sets a value indicating whether to show the payload content.
/// </summary>
public bool ShowPayload { get; set; }

/// <summary>
/// Gets the payload as a UTF-8 string if possible, otherwise returns hex representation.
/// </summary>
public string PayloadString
{
get
{
if (this.Message?.Payload == null)
{
return string.Empty;
}

try
{
return System.Text.Encoding.UTF8.GetString(this.Message.Payload);
}
catch
{
return BitConverter.ToString(this.Message.Payload).Replace("-", " ");
}
}
}

/// <summary>
/// Gets the formatted add date time.
/// </summary>
public DateTime AddDateTime => DateTimeOffset.FromUnixTimeSeconds(this.Message?.AddDateTime ?? 0).DateTime;

/// <summary>
/// Gets the formatted close date time, if available.
/// </summary>
public DateTime? CloseDateTime => this.Message?.CloseDateTime.HasValue == true
? DateTimeOffset.FromUnixTimeSeconds(this.Message.CloseDateTime.Value).DateTime
: null;

/// <summary>
/// Gets the formatted expiry date time, if available.
/// </summary>
public DateTime? ExpiryDateTime => this.Message?.ExpiryDateTime.HasValue == true
? DateTimeOffset.FromUnixTimeSeconds(this.Message.ExpiryDateTime.Value).DateTime
: null;
}
64 changes: 64 additions & 0 deletions Procession.AdminWeb/Models/MessageListViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// <copyright file="MessageListViewModel.cs" company="Michael Silver">
// Copyright (c) Michael Silver. All rights reserved.
// </copyright>

using Procession.Common.Models;
using Procession.Server.Common.Models;

namespace Procession.AdminWeb.Models;

/// <summary>
/// View model for the message list page with paging and filtering.
/// </summary>
public class MessageListViewModel
{
/// <summary>
/// Gets or sets the list of messages for the current page.
/// </summary>
public List<Message> Messages { get; set; } = new List<Message>();

/// <summary>
/// Gets or sets the current page number (1-based).
/// </summary>
public int CurrentPage { get; set; } = 1;

/// <summary>
/// Gets or sets the page size.
/// </summary>
public int PageSize { get; set; } = 50;

/// <summary>
/// Gets or sets the total count of messages matching the filter.
/// </summary>
public int TotalCount { get; set; }

/// <summary>
/// Gets or sets the selected queue ID for filtering (null for all queues).
/// </summary>
public long? SelectedQueueId { get; set; }

/// <summary>
/// Gets or sets the processed filter (null for all, true for processed only, false for unprocessed only).
/// </summary>
public bool? ProcessedOnly { get; set; }

/// <summary>
/// Gets or sets the list of available queues for the filter dropdown.
/// </summary>
public List<QueueInfo> AvailableQueues { get; set; } = new List<QueueInfo>();

/// <summary>
/// Gets the total number of pages.
/// </summary>
public int TotalPages => (int)Math.Ceiling((double)this.TotalCount / this.PageSize);

/// <summary>
/// Gets a value indicating whether there is a previous page.
/// </summary>
public bool HasPreviousPage => this.CurrentPage > 1;

/// <summary>
/// Gets a value indicating whether there is a next page.
/// </summary>
public bool HasNextPage => this.CurrentPage < this.TotalPages;
}
31 changes: 31 additions & 0 deletions Procession.AdminWeb/Procession.AdminWeb.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DocumentationFile>$(OutputPath)$(AssemblyName).xml</DocumentationFile>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<AdditionalFiles Include="stylecop.json" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Procession.Server.Common\Procession.Server.Common.csproj" />
<ProjectReference Include="..\Procession.Common\Procession.Common.csproj" />
<ProjectReference Include="..\Procession.Sqlite\Procession.Sqlite.csproj" />
<ProjectReference Include="..\Procession.Postgres\Procession.Postgres.csproj" />
</ItemGroup>

</Project>
Loading