From e9e09d0cbf600338c6913f5478ca2a4ecd111b15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20A=2EP?= <53834183+Jossec101@users.noreply.github.com> Date: Thu, 19 Feb 2026 21:26:19 +0100 Subject: [PATCH] [GEN-1863] add pagination support to ChannelOperationRequestRepository + new filtering Add GetPaginatedAsync method with comprehensive filtering options including status, request type, node IDs, wallet ID, user ID, date range, and excluded IDs. Update interface and implement pagination UI with filters in ChannelRequests page. stack-info: PR: https://github.com/Elenpay/NodeGuard/pull/479, branch: Jossec101/stack/12 --- .../ChannelOperationRequestRepository.cs | 81 +++++++ .../IChannelOperationRequestRepository.cs | 13 ++ src/Pages/ChannelRequests.razor | 197 +++++++++++++++++- 3 files changed, 290 insertions(+), 1 deletion(-) diff --git a/src/Data/Repositories/ChannelOperationRequestRepository.cs b/src/Data/Repositories/ChannelOperationRequestRepository.cs index a98bfb0d..34744987 100644 --- a/src/Data/Repositories/ChannelOperationRequestRepository.cs +++ b/src/Data/Repositories/ChannelOperationRequestRepository.cs @@ -235,5 +235,86 @@ public async Task> GetPendingRequests() .Include(x => x.Utxos).AsSplitQuery() .ToListAsync(); } + + public async Task<(List requests, int totalCount)> GetPaginatedAsync( + int pageNumber, + int pageSize, + ChannelOperationRequestStatus? status = null, + OperationRequestType? requestType = null, + int? sourceNodeId = null, + int? destNodeId = null, + int? walletId = null, + string? userId = null, + DateTimeOffset? fromDate = null, + DateTimeOffset? toDate = null, + IEnumerable? excludedIds = null) + { + await using var applicationDbContext = await _dbContextFactory.CreateDbContextAsync(); + + var query = applicationDbContext.ChannelOperationRequests + .Include(request => request.Wallet) + .Include(request => request.SourceNode) + .Include(request => request.DestNode) + .Include(request => request.ChannelOperationRequestPsbts) + .Include(request => request.User) + .Include(x => x.Utxos) + .AsSplitQuery() + .AsQueryable(); + + if (status.HasValue) + { + query = query.Where(r => r.Status == status.Value); + } + + if (requestType.HasValue) + { + query = query.Where(r => r.RequestType == requestType.Value); + } + + if (sourceNodeId.HasValue) + { + query = query.Where(r => r.SourceNodeId == sourceNodeId.Value); + } + + if (destNodeId.HasValue) + { + query = query.Where(r => r.DestNodeId == destNodeId.Value); + } + + if (walletId.HasValue) + { + query = query.Where(r => r.WalletId == walletId.Value); + } + + if (!string.IsNullOrEmpty(userId)) + { + query = query.Where(r => r.UserId == userId); + } + + if (fromDate.HasValue) + { + query = query.Where(r => r.CreationDatetime >= fromDate.Value); + } + + if (toDate.HasValue) + { + query = query.Where(r => r.CreationDatetime <= toDate.Value); + } + + if (excludedIds != null && excludedIds.Any()) + { + query = query.Where(r => !excludedIds.Contains(r.Id)); + } + + var totalCount = await query.CountAsync(); + + var requests = await query + .OrderByDescending(r => r.CreationDatetime) + .Skip((pageNumber - 1) * pageSize) + .Take(pageSize) + .ToListAsync(); + + return (requests, totalCount); + } } } \ No newline at end of file diff --git a/src/Data/Repositories/Interfaces/IChannelOperationRequestRepository.cs b/src/Data/Repositories/Interfaces/IChannelOperationRequestRepository.cs index 34d050a6..56dbcb64 100644 --- a/src/Data/Repositories/Interfaces/IChannelOperationRequestRepository.cs +++ b/src/Data/Repositories/Interfaces/IChannelOperationRequestRepository.cs @@ -44,4 +44,17 @@ public interface IChannelOperationRequestRepository : IBitcoinRequestRepository /// /// Task> GetPendingRequests(); + + Task<(List requests, int totalCount)> GetPaginatedAsync( + int pageNumber, + int pageSize, + ChannelOperationRequestStatus? status = null, + OperationRequestType? requestType = null, + int? sourceNodeId = null, + int? destNodeId = null, + int? walletId = null, + string? userId = null, + DateTimeOffset? fromDate = null, + DateTimeOffset? toDate = null, + IEnumerable? excludedIds = null); } \ No newline at end of file diff --git a/src/Pages/ChannelRequests.razor b/src/Pages/ChannelRequests.razor index f522affa..09aa05aa 100644 --- a/src/Pages/ChannelRequests.razor +++ b/src/Pages/ChannelRequests.razor @@ -4,6 +4,7 @@ @using Quartz @using Humanizer @using NBitcoin +@using Blazorise.Components @using NodeGuard.Jobs @using Google.Protobuf @using NBXplorer.Models @@ -287,10 +288,127 @@
+ + + + Status + + + + + + Type + + + + + + Source Node + + + + + + Dest Node + + + + + + Wallet + + + + + + User + + + + + + From + + + + + + To + + + + + + + +@* TODO: Convert this grid to paginated ReadData to avoid loading all records in memory. *@ @@ -455,10 +573,30 @@ @inject IPriceConversionService PriceConversionService @inject IAuditService AuditService @inject IHttpContextAccessor HttpContextAccessor +@inject IApplicationUserRepository ApplicationUserRepository @code { private List? _channelRequests; private List? _allRequests; + private int _totalAllRequests; + private List _availableUsers = new(); + private List _availableNodes = new(); + + // Filter state + private ChannelOperationRequestStatus? _statusFilter; + private OperationRequestType? _requestTypeFilter; + private int? _sourceNodeFilter; + private int? _destNodeFilter; + private int? _walletFilter; + private string? _userFilter; + private int _filtersResetKey; + private DateTime? _fromDate; + private DateTime? _toDate; + + // Filter options + private List _statusOptions = new() { null }; + private List _requestTypeOptions = new() { null }; + private ChannelOperationRequest? _selectedRequest; private ChannelOperationRequestStatus _selectedStatus; private ChannelOperationRequest _selectedRequestForMarkingAsFailed; @@ -1136,8 +1274,65 @@ private async Task RefreshChannelRequestsInformation() { await FetchRequests(); + if (_allRequestsDatagrid != null) + { + await _allRequestsDatagrid.Reload(); + } StateHasChanged(); } + + private async Task OnAllRequestsReadData(DataGridReadDataEventArgs e) + { + if (!e.CancellationToken.IsCancellationRequested) + { + var fromDateOffset = _fromDate.HasValue ? new DateTimeOffset(_fromDate.Value, TimeSpan.Zero) : (DateTimeOffset?)null; + var toDateOffset = _toDate.HasValue ? new DateTimeOffset(_toDate.Value.AddDays(1).AddSeconds(-1), TimeSpan.Zero) : (DateTimeOffset?)null; + + var excludedIds = _channelRequests?.Select(r => r.Id) ?? Enumerable.Empty(); + + var (requests, totalCount) = await ChannelOperationRequestRepository.GetPaginatedAsync( + e.Page, + e.PageSize, + _statusFilter, + _requestTypeFilter, + _sourceNodeFilter, + _destNodeFilter, + _walletFilter, + _userFilter, + fromDateOffset, + toDateOffset, + excludedIds); + + _allRequests = requests; + _totalAllRequests = totalCount; + } + } + + private async Task OnFiltersChanged() + { + if (_allRequestsDatagrid != null) + { + await _allRequestsDatagrid.Reload(); + } + } + + private async Task ClearAllFilters() + { + _statusFilter = null; + _requestTypeFilter = null; + _sourceNodeFilter = null; + _destNodeFilter = null; + _walletFilter = null; + _userFilter = null; + _fromDate = null; + _toDate = null; + _filtersResetKey++; + + if (_allRequestsDatagrid != null) + { + await _allRequestsDatagrid.Reload(); + } + } private async Task OnChangelessChanged(bool value) {