From 425fb343174676f19a81de3fd860e2407dd0f4b4 Mon Sep 17 00:00:00 2001 From: Jason Dove <1695733+jasongdove@users.noreply.github.com> Date: Fri, 31 Oct 2025 09:05:25 -0500 Subject: [PATCH] show playout warnings count in left menu (#2583) --- CHANGELOG.md | 3 + .../Playouts/Commands/DeletePlayoutHandler.cs | 8 +- .../Queries/GetPlayoutWarningsCount.cs | 3 + .../Queries/GetPlayoutWarningsCountHandler.cs | 15 ++++ ErsatzTV/Shared/MainLayout.razor | 74 +++++++++++++++---- 5 files changed, 89 insertions(+), 14 deletions(-) create mode 100644 ErsatzTV.Application/Playouts/Queries/GetPlayoutWarningsCount.cs create mode 100644 ErsatzTV.Application/Playouts/Queries/GetPlayoutWarningsCountHandler.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index c8a689e69..e2dcdcbfd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## [Unreleased] +### Added +- Show playout warnings count badge in left menu + ### Fixed - Fix HLS Direct playback with Jellyfin 10.11 - Fix remote stream scripts (parsing issue with spaces and quotes) diff --git a/ErsatzTV.Application/Playouts/Commands/DeletePlayoutHandler.cs b/ErsatzTV.Application/Playouts/Commands/DeletePlayoutHandler.cs index 33516fcbd..2467aade2 100644 --- a/ErsatzTV.Application/Playouts/Commands/DeletePlayoutHandler.cs +++ b/ErsatzTV.Application/Playouts/Commands/DeletePlayoutHandler.cs @@ -3,6 +3,7 @@ using ErsatzTV.Application.Channels; using ErsatzTV.Core; using ErsatzTV.Core.Domain; using ErsatzTV.Core.Interfaces.Metadata; +using ErsatzTV.Core.Notifications; using ErsatzTV.Infrastructure.Data; using ErsatzTV.Infrastructure.Extensions; using Microsoft.EntityFrameworkCore; @@ -13,16 +14,19 @@ public class DeletePlayoutHandler : IRequestHandler _dbContextFactory; private readonly ILocalFileSystem _localFileSystem; + private readonly IMediator _mediator; private readonly ChannelWriter _workerChannel; public DeletePlayoutHandler( ChannelWriter workerChannel, IDbContextFactory dbContextFactory, - ILocalFileSystem localFileSystem) + ILocalFileSystem localFileSystem, + IMediator mediator) { _workerChannel = workerChannel; _dbContextFactory = dbContextFactory; _localFileSystem = localFileSystem; + _mediator = mediator; } public async Task> Handle(DeletePlayout request, CancellationToken cancellationToken) @@ -47,6 +51,8 @@ public class DeletePlayoutHandler : IRequestHandler; diff --git a/ErsatzTV.Application/Playouts/Queries/GetPlayoutWarningsCountHandler.cs b/ErsatzTV.Application/Playouts/Queries/GetPlayoutWarningsCountHandler.cs new file mode 100644 index 000000000..831718fe0 --- /dev/null +++ b/ErsatzTV.Application/Playouts/Queries/GetPlayoutWarningsCountHandler.cs @@ -0,0 +1,15 @@ +using ErsatzTV.Infrastructure.Data; +using Microsoft.EntityFrameworkCore; + +namespace ErsatzTV.Application.Playouts; + +public class GetPlayoutWarningsCountHandler(IDbContextFactory dbContextFactory) + : IRequestHandler +{ + public async Task Handle(GetPlayoutWarningsCount request, CancellationToken cancellationToken) + { + await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); + return await dbContext.PlayoutBuildStatus + .CountAsync(bs => !bs.Success, cancellationToken); + } +} diff --git a/ErsatzTV/Shared/MainLayout.razor b/ErsatzTV/Shared/MainLayout.razor index 4a7855397..c8c65be2a 100644 --- a/ErsatzTV/Shared/MainLayout.razor +++ b/ErsatzTV/Shared/MainLayout.razor @@ -1,9 +1,11 @@ @inherits LayoutComponentBase @using System.Reflection @using ErsatzTV.Application.Configuration +@using ErsatzTV.Application.Playouts @using ErsatzTV.Application.Search @using ErsatzTV.Core.Health @using ErsatzTV.Core.Interfaces.Search +@using ErsatzTV.Core.Notifications @using ErsatzTV.Extensions @using MediatR.Courier @implements IDisposable @@ -144,13 +146,42 @@ Trakt Lists Filler Presets - - Schedules - Blocks - Templates - Decos - Deco Templates - Playouts + + + @if (_playoutWarnings > 0) + { +
+ Scheduling + +
+ } + else + { + @:Scheduling + } +
+ + Schedules + Blocks + Templates + Decos + Deco Templates + + @if (_playoutWarnings > 0) + { + + Playouts + + } + else + { + @:Playouts + } + +
FFmpeg @@ -241,6 +272,7 @@ private bool _drawerIsOpen = true; private bool _isOpen; private List _searchTargets; + private int _playoutWarnings; private int _errors; private int _warnings; private bool _isDarkMode = true; @@ -253,6 +285,7 @@ SearchTargets.OnSearchTargetsChanged += OnSearchTargetsChanged; Courier.Subscribe(HandleHealthCheckSummary); + Courier.Subscribe(HandlePlayoutUpdated); } protected override async Task OnInitializedAsync() @@ -385,14 +418,16 @@ await base.OnParametersSetAsync(); _query = NavigationManager.Uri.GetSearchQuery(); - if (SystemStartup.IsDatabaseReady && _searchTargets is null) + if (SystemStartup.IsDatabaseReady) { - _searchTargets = await Mediator.Send(new QuerySearchTargets(), token); - } + _searchTargets ??= await Mediator.Send(new QuerySearchTargets(), token); - _isDarkMode = await Mediator.Send(new GetConfigElementByKey(ConfigElementKey.PagesIsDarkMode), token) - .MapT(result => !bool.TryParse(result.Value, out bool value) || value) - .IfNoneAsync(true); + _isDarkMode = await Mediator.Send(new GetConfigElementByKey(ConfigElementKey.PagesIsDarkMode), token) + .MapT(result => !bool.TryParse(result.Value, out bool value) || value) + .IfNoneAsync(true); + + _playoutWarnings = await Mediator.Send(new GetPlayoutWarningsCount(), token); + } } catch (OperationCanceledException) { @@ -481,4 +516,17 @@ } } + private async Task HandlePlayoutUpdated(PlayoutUpdatedNotification _, CancellationToken cancellationToken) + { + try + { + _playoutWarnings = await Mediator.Send(new GetPlayoutWarningsCount(), cancellationToken); + await InvokeAsync(StateHasChanged); + } + catch (Exception) + { + // do nothing + } + } + }