From 9d0544f982e3a0ab773d02b45f66c13b16b84c09 Mon Sep 17 00:00:00 2001 From: Smit Patel Date: Fri, 6 Oct 2023 14:45:34 -0700 Subject: [PATCH] Refactor common code for logs pages --- .../Components/Pages/ContainerLogs.razor | 202 ++--------------- .../Components/Pages/Containers.razor | 2 +- .../Components/Pages/ExecutableLogs.razor | 207 ++---------------- .../Components/Pages/ProjectLogs.razor | 207 ++---------------- .../Components/Pages/ResourceLogsBase.razor | 24 ++ .../Pages/ResourceLogsBase.razor.cs | 204 +++++++++++++++++ 6 files changed, 276 insertions(+), 570 deletions(-) create mode 100644 src/Aspire.Dashboard/Components/Pages/ResourceLogsBase.razor create mode 100644 src/Aspire.Dashboard/Components/Pages/ResourceLogsBase.razor.cs diff --git a/src/Aspire.Dashboard/Components/Pages/ContainerLogs.razor b/src/Aspire.Dashboard/Components/Pages/ContainerLogs.razor index 7fc0177de6..6a70f75485 100644 --- a/src/Aspire.Dashboard/Components/Pages/ContainerLogs.razor +++ b/src/Aspire.Dashboard/Components/Pages/ContainerLogs.razor @@ -1,193 +1,25 @@ -@page "/ContainerLogs/{containerid?}" +@page "/ContainerLogs/{containername?}" @using Aspire.Dashboard.Model -@inject IDashboardViewModelService DashboardViewModelService -@inject IJSRuntime JS -@inject NavigationManager NavigationManager -@implements IAsyncDisposable +@inherits ResourceLogsBase -Microsoft.Aspire Container Logs - -

Container Logs

- -
- - - - @_status - - - -
- - @code { +@{ + base.BuildRenderTree(__builder); +} +@code { [Parameter] - public string? ContainerId { get; set; } - - private ContainerViewModel? _selectedContainer; - private Dictionary _containerNameMapping = new(); - private IEnumerable Containers => _containerNameMapping.Select(kvp => kvp.Value).OrderBy(c => c.Name); - private LogViewer? _logViewer; - private CancellationTokenSource _watchContainersTokenSource = new CancellationTokenSource(); - private CancellationTokenSource? _watchLogsTokenSource; - private string _status = LogStatus.Initializing; - - protected override async Task OnInitializedAsync() - { - _status = LogStatus.LoadingContainers; - - var initialList = await DashboardViewModelService.GetContainersAsync(); - - foreach (var result in initialList) - { - _containerNameMapping[result.Name] = result; - } - - if (ContainerId is not null) - { - _selectedContainer = initialList?.FirstOrDefault(c => string.Equals(ContainerId, c.ContainerId, StringComparison.Ordinal)); - } - else if (initialList?.Count > 0) - { - _selectedContainer = initialList[0]; - } - - await LoadLogsAsync(); - - _ = Task.Run(async () => - { - await foreach (var componentChanged in DashboardViewModelService.WatchContainersAsync(existingContainers: initialList?.Select(t => t.NamespacedName), cancellationToken: _watchContainersTokenSource.Token)) - { - await OnContainerListChanged(componentChanged.ObjectChangeType, componentChanged.Component); - } - }); - } - - private Task ClearLogsAsync() - => _logViewer is not null ? _logViewer.ClearLogsAsync() : Task.CompletedTask; - - private async ValueTask LoadLogsAsync() - { - if (_selectedContainer is null) - { - _status = LogStatus.NoContainerSelected; - } - else if (_logViewer is null) - { - _status = LogStatus.InitializingLogViewer; - } - else - { - _watchLogsTokenSource = new CancellationTokenSource(); - if (await _selectedContainer.LogSource.StartAsync(_watchLogsTokenSource.Token)) - { - _ = Task.Run(async () => - { - await _logViewer.WatchLogsAsync(() => _selectedContainer.LogSource.WatchOutputLogAsync(_watchLogsTokenSource.Token), LogEntryType.Default); - }); - - _ = Task.Run(async () => - { - await _logViewer.WatchLogsAsync(() => _selectedContainer.LogSource.WatchErrorLogAsync(_watchLogsTokenSource.Token), LogEntryType.Error); - }); - - _status = LogStatus.WatchingLogs; - } - else - { - _watchLogsTokenSource = null; - _status = LogStatus.FailedToInitialize; - } - } - } - - private async Task HandleSelectedOptionChangedAsync() - { - if (_selectedContainer is not null) - { - // Change the URL - NavigationManager.NavigateTo($"/containerLogs/{_selectedContainer.ContainerId}"); - await StopWatchingLogsAsync(); - await ClearLogsAsync(); - await LoadLogsAsync(); - } - } - - private async Task OnContainerListChanged(ObjectChangeType changeType, ContainerViewModel containerViewModel) - { - if (changeType == ObjectChangeType.Added) - { - _containerNameMapping[containerViewModel.Name] = containerViewModel; - - if (_selectedContainer is null) - { - if (string.IsNullOrEmpty(ContainerId) || string.Equals(ContainerId, containerViewModel.ContainerId, StringComparison.Ordinal)) - { - _selectedContainer = containerViewModel; - await LoadLogsAsync(); - } - } - } - else if (changeType == ObjectChangeType.Modified) - { - _containerNameMapping[containerViewModel.Name] = containerViewModel; - } - else if (changeType == ObjectChangeType.Deleted) - { - _containerNameMapping.Remove(containerViewModel.Name); - if (string.Equals(_selectedContainer?.Name, containerViewModel.Name, StringComparison.Ordinal)) - { - if (_containerNameMapping.Count > 0) - { - _selectedContainer = Containers.First(); - await HandleSelectedOptionChangedAsync(); - } - } - } - - await InvokeAsync(StateHasChanged); - } - - private static string GetContainerDisplayText(ContainerViewModel container) - { - string stateText = ""; - if (string.IsNullOrEmpty(container.State)) - { - stateText = " (Unknown State)"; - } - else if (container.State != "Running") - { - stateText = $" ({container.State})"; - } - return $"{container.Name}{stateText}"; - } + public string? ContainerName { get; set; } - public async ValueTask DisposeAsync() - { - await DisposeWatchContainersTokenSource(); - await StopWatchingLogsAsync(); - } + protected override string? ResourceName => ContainerName; + protected override string ResourceType => "Container"; + protected override string LoadingResourcesMessage => LogStatus.LoadingContainers; + protected override string NoResourceSelectedMessage => LogStatus.NoContainerSelected; + protected override string LogsNotAvailableMessage => LogStatus.FailedToInitialize; + protected override string UrlPrefix => "/ContainerLogs"; - private async Task DisposeWatchContainersTokenSource() - { - await _watchContainersTokenSource.CancelAsync(); - _watchContainersTokenSource.Dispose(); - } + protected override Task> GetResources(IDashboardViewModelService dashboardViewModelService) + => dashboardViewModelService.GetContainersAsync(); - private async Task StopWatchingLogsAsync() - { - if (_watchLogsTokenSource is not null) - { - await _watchLogsTokenSource.CancelAsync(); - _watchLogsTokenSource.Dispose(); - // The token source only gets created if selected container is not null - await _selectedContainer!.LogSource.StopAsync(); - _watchLogsTokenSource = null; - } - } + protected override IAsyncEnumerable> WatchResources(IDashboardViewModelService dashboardViewModelService, IEnumerable? initialList, CancellationToken cancellationToken) + => dashboardViewModelService.WatchContainersAsync(initialList, cancellationToken); } diff --git a/src/Aspire.Dashboard/Components/Pages/Containers.razor b/src/Aspire.Dashboard/Components/Pages/Containers.razor index 12acebb3ac..153884cefe 100644 --- a/src/Aspire.Dashboard/Components/Pages/Containers.razor +++ b/src/Aspire.Dashboard/Components/Pages/Containers.razor @@ -40,7 +40,7 @@ OnClick="async() => await ShowEnvironmentVariables(context)">View - View + View diff --git a/src/Aspire.Dashboard/Components/Pages/ExecutableLogs.razor b/src/Aspire.Dashboard/Components/Pages/ExecutableLogs.razor index c924bf43fb..d35f57ba79 100644 --- a/src/Aspire.Dashboard/Components/Pages/ExecutableLogs.razor +++ b/src/Aspire.Dashboard/Components/Pages/ExecutableLogs.razor @@ -1,202 +1,25 @@ @page "/ExecutableLogs/{executablename?}" @using Aspire.Dashboard.Model; -@implements IAsyncDisposable; -@inject IDashboardViewModelService DashboardViewModelService -@inject IJSRuntime JS -@inject NavigationManager NavigationManager +@inherits ResourceLogsBase -Microsoft.Aspire Executable Logs - -

Executable Logs

- -
- - - - @_status - - - -
+@{ + base.BuildRenderTree(__builder); +} - @code { +@code { [Parameter] public string? ExecutableName { get; set; } - private ExecutableViewModel? _selectedExecutable; - private readonly Dictionary _executableNameMapping = new(); - private IEnumerable Executables => _executableNameMapping.Select(kvp => kvp.Value).OrderBy(p => p.Name); - private LogViewer? _logViewer; - private CancellationTokenSource _watchExecutablesTokenSource = new CancellationTokenSource(); - private CancellationTokenSource? _watchLogsTokenSource; - private string _status = LogStatus.Initializing; - - protected override async Task OnInitializedAsync() - { - _status = LogStatus.LoadingExecutables; - - var initialList = await DashboardViewModelService.GetExecutablesAsync(); - - foreach (var result in initialList) - { - _executableNameMapping[result.Name] = result; - } - - if (ExecutableName is not null) - { - _selectedExecutable = initialList?.FirstOrDefault(c => string.Equals(ExecutableName, c.Name, StringComparison.Ordinal)); - } - else if (initialList?.Count > 0) - { - _selectedExecutable = initialList[0]; - } - - await LoadLogsAsync(); - - _ = Task.Run(async () => - { - await foreach (var componentChanged in DashboardViewModelService.WatchExecutablesAsync(existingExecutables: initialList?.Select(t => t.NamespacedName), cancellationToken: _watchExecutablesTokenSource.Token)) - { - await OnExecutableListChanged(componentChanged.ObjectChangeType, componentChanged.Component); - } - }); - } - - private Task ClearLogsAsync() - => _logViewer is not null ? _logViewer.ClearLogsAsync() : Task.CompletedTask; - - private async ValueTask LoadLogsAsync() - { - if (_selectedExecutable is null) - { - _status = LogStatus.NoExecutableSelected; - } - else if (_logViewer is null) - { - _status = LogStatus.InitializingLogViewer; - } - else - { - _watchLogsTokenSource = new CancellationTokenSource(); - if (await _selectedExecutable.LogSource.StartAsync(_watchLogsTokenSource.Token)) - { - _ = Task.Run(async () => - { - await _logViewer.WatchLogsAsync(() => _selectedExecutable.LogSource.WatchOutputLogAsync(_watchLogsTokenSource.Token), LogEntryType.Default); - }); - - _ = Task.Run(async () => - { - await _logViewer.WatchLogsAsync(() => _selectedExecutable.LogSource.WatchErrorLogAsync(_watchLogsTokenSource.Token), LogEntryType.Error); - }); - - _status = LogStatus.WatchingLogs; - } - else - { - _watchLogsTokenSource = null; - _status = LogStatus.LogsNotYetAvailable; - } - } - } - - private async Task HandleSelectedOptionChangedAsync() - { - if (_selectedExecutable is not null) - { - NavigationManager.NavigateTo($"/ExecutableLogs/{_selectedExecutable.Name}"); - await StopWatchingLogsAsync(); - await ClearLogsAsync(); - await LoadLogsAsync(); - } - } - - private async Task OnExecutableListChanged(ObjectChangeType changeType, ExecutableViewModel ExecutableViewModel) - { - if (changeType == ObjectChangeType.Added) - { - _executableNameMapping[ExecutableViewModel.Name] = ExecutableViewModel; - - if (_selectedExecutable is null) - { - if (string.IsNullOrEmpty(ExecutableName) || string.Equals(ExecutableName, ExecutableViewModel.Name, StringComparison.Ordinal)) - { - _selectedExecutable = ExecutableViewModel; - await LoadLogsAsync(); - } - } - } - else if (changeType == ObjectChangeType.Modified) - { - _executableNameMapping[ExecutableViewModel.Name] = ExecutableViewModel; - - if (string.Equals(_selectedExecutable?.Name, ExecutableViewModel.Name, StringComparison.Ordinal)) - { - var originalSelection = _selectedExecutable; - _selectedExecutable = ExecutableViewModel; - - if (_watchLogsTokenSource is null) - { - await LoadLogsAsync(); - } - } - } - else if (changeType == ObjectChangeType.Deleted) - { - _executableNameMapping.Remove(ExecutableViewModel.Name); - if (string.Equals(_selectedExecutable?.Name, ExecutableViewModel.Name, StringComparison.Ordinal)) - { - if (_executableNameMapping.Count > 0) - { - _selectedExecutable = Executables.First(); - await HandleSelectedOptionChangedAsync(); - } - } - } - - await InvokeAsync(StateHasChanged); - } - - private static string GetExecutableDisplayText(ExecutableViewModel executable) - { - string stateText = ""; - if (string.IsNullOrEmpty(executable.State)) - { - stateText = " (Unknown State)"; - } - else if (executable.State != "Running") - { - stateText = $" ({executable.State})"; - } - return $"{executable.Name}{stateText}"; - } - - public async ValueTask DisposeAsync() - { - await DisposeWatchExecutablesTokenSource(); - await StopWatchingLogsAsync(); - } + protected override string? ResourceName => ExecutableName; + protected override string ResourceType => "Executable"; + protected override string LoadingResourcesMessage => LogStatus.LoadingExecutables; + protected override string NoResourceSelectedMessage => LogStatus.NoExecutableSelected; + protected override string LogsNotAvailableMessage => LogStatus.LogsNotYetAvailable; + protected override string UrlPrefix => "/ExecutableLogs"; - private async Task DisposeWatchExecutablesTokenSource() - { - await _watchExecutablesTokenSource.CancelAsync(); - _watchExecutablesTokenSource.Dispose(); - } + protected override Task> GetResources(IDashboardViewModelService dashboardViewModelService) + => dashboardViewModelService.GetExecutablesAsync(); - private async Task StopWatchingLogsAsync() - { - if (_watchLogsTokenSource is not null) - { - await _watchLogsTokenSource.CancelAsync(); - _watchLogsTokenSource.Dispose(); - // The token source only gets created if selected executable is not null - await _selectedExecutable!.LogSource.StopAsync(); - _watchLogsTokenSource = null; - } - } + protected override IAsyncEnumerable> WatchResources(IDashboardViewModelService dashboardViewModelService, IEnumerable? initialList, CancellationToken cancellationToken) + => dashboardViewModelService.WatchExecutablesAsync(initialList, cancellationToken); } diff --git a/src/Aspire.Dashboard/Components/Pages/ProjectLogs.razor b/src/Aspire.Dashboard/Components/Pages/ProjectLogs.razor index ceeb316c45..b7942b849f 100644 --- a/src/Aspire.Dashboard/Components/Pages/ProjectLogs.razor +++ b/src/Aspire.Dashboard/Components/Pages/ProjectLogs.razor @@ -1,202 +1,25 @@ @page "/ProjectLogs/{projectname?}" @using Aspire.Dashboard.Model; -@implements IAsyncDisposable; -@inject IDashboardViewModelService DashboardViewModelService -@inject IJSRuntime JS -@inject NavigationManager NavigationManager +@inherits ResourceLogsBase -Microsoft.Aspire Project Logs - -

Project Logs

- -
- - - - @_status - - - -
+@{ + base.BuildRenderTree(__builder); +} - @code { +@code { [Parameter] public string? ProjectName { get; set; } - private ProjectViewModel? _selectedProject; - private readonly Dictionary _projectNameMapping = new(); - private IEnumerable Projects => _projectNameMapping.Select(kvp => kvp.Value).OrderBy(p => p.Name); - private LogViewer? _logViewer; - private CancellationTokenSource _watchProjectsTokenSource = new CancellationTokenSource(); - private CancellationTokenSource? _watchLogsTokenSource; - private string _status = LogStatus.Initializing; - - protected override async Task OnInitializedAsync() - { - _status = LogStatus.LoadingProjects; - - var initialList = await DashboardViewModelService.GetProjectsAsync(); - - foreach (var result in initialList) - { - _projectNameMapping[result.Name] = result; - } - - if (ProjectName is not null) - { - _selectedProject = initialList?.FirstOrDefault(c => string.Equals(ProjectName, c.Name, StringComparison.Ordinal)); - } - else if (initialList?.Count > 0) - { - _selectedProject = initialList[0]; - } - - await LoadLogsAsync(); - - _ = Task.Run(async () => - { - await foreach (var componentChanged in DashboardViewModelService.WatchProjectsAsync(existingProjects: initialList?.Select(t => t.NamespacedName), cancellationToken: _watchProjectsTokenSource.Token)) - { - await OnProjectListChanged(componentChanged.ObjectChangeType, componentChanged.Component); - } - }); - } - - private Task ClearLogsAsync() - => _logViewer is not null ? _logViewer.ClearLogsAsync() : Task.CompletedTask; - - private async ValueTask LoadLogsAsync() - { - if (_selectedProject is null) - { - _status = LogStatus.NoProjectSelected; - } - else if (_logViewer is null) - { - _status = LogStatus.InitializingLogViewer; - } - else - { - _watchLogsTokenSource = new CancellationTokenSource(); - if (await _selectedProject.LogSource.StartAsync(_watchLogsTokenSource.Token)) - { - _ = Task.Run(async () => - { - await _logViewer.WatchLogsAsync(() => _selectedProject.LogSource.WatchOutputLogAsync(_watchLogsTokenSource.Token), LogEntryType.Default); - }); - - _ = Task.Run(async () => - { - await _logViewer.WatchLogsAsync(() => _selectedProject.LogSource.WatchErrorLogAsync(_watchLogsTokenSource.Token), LogEntryType.Error); - }); - - _status = LogStatus.WatchingLogs; - } - else - { - _watchLogsTokenSource = null; - _status = LogStatus.LogsNotYetAvailable; - } - } - } - - private async Task HandleSelectedOptionChangedAsync() - { - if (_selectedProject is not null) - { - NavigationManager.NavigateTo($"/ProjectLogs/{_selectedProject.Name}"); - await StopWatchingLogsAsync(); - await ClearLogsAsync(); - await LoadLogsAsync(); - } - } - - private async Task OnProjectListChanged(ObjectChangeType changeType, ProjectViewModel projectViewModel) - { - if (changeType == ObjectChangeType.Added) - { - _projectNameMapping[projectViewModel.Name] = projectViewModel; - - if (_selectedProject is null) - { - if (string.IsNullOrEmpty(ProjectName) || string.Equals(ProjectName, projectViewModel.Name, StringComparison.Ordinal)) - { - _selectedProject = projectViewModel; - await LoadLogsAsync(); - } - } - } - else if (changeType == ObjectChangeType.Modified) - { - _projectNameMapping[projectViewModel.Name] = projectViewModel; - - if (string.Equals(_selectedProject?.Name, projectViewModel.Name, StringComparison.Ordinal)) - { - var originalSelection = _selectedProject; - _selectedProject = projectViewModel; - - if (_watchLogsTokenSource is null) - { - await LoadLogsAsync(); - } - } - } - else if (changeType == ObjectChangeType.Deleted) - { - _projectNameMapping.Remove(projectViewModel.Name); - if (string.Equals(_selectedProject?.Name, projectViewModel.Name, StringComparison.Ordinal)) - { - if (_projectNameMapping.Count > 0) - { - _selectedProject = Projects.First(); - await HandleSelectedOptionChangedAsync(); - } - } - } - - await InvokeAsync(StateHasChanged); - } - - private static string GetProjectDisplayText(ProjectViewModel project) - { - string stateText = ""; - if (string.IsNullOrEmpty(project.State)) - { - stateText = " (Unknown State)"; - } - else if (project.State != "Running") - { - stateText = $" ({project.State})"; - } - return $"{project.Name}{stateText}"; - } - - public async ValueTask DisposeAsync() - { - await DisposeWatchProjectsTokenSource(); - await StopWatchingLogsAsync(); - } + protected override string? ResourceName => ProjectName; + protected override string ResourceType => "Project"; + protected override string LoadingResourcesMessage => LogStatus.LoadingProjects; + protected override string NoResourceSelectedMessage => LogStatus.NoProjectSelected; + protected override string LogsNotAvailableMessage => LogStatus.LogsNotYetAvailable; + protected override string UrlPrefix => "/ProjectLogs"; - private async Task DisposeWatchProjectsTokenSource() - { - await _watchProjectsTokenSource.CancelAsync(); - _watchProjectsTokenSource.Dispose(); - } + protected override Task> GetResources(IDashboardViewModelService dashboardViewModelService) + => dashboardViewModelService.GetProjectsAsync(); - private async Task StopWatchingLogsAsync() - { - if (_watchLogsTokenSource is not null) - { - await _watchLogsTokenSource.CancelAsync(); - _watchLogsTokenSource.Dispose(); - // The token source only gets created if selected project is not null - await _selectedProject!.LogSource.StopAsync(); - _watchLogsTokenSource = null; - } - } + protected override IAsyncEnumerable> WatchResources(IDashboardViewModelService dashboardViewModelService, IEnumerable? initialList, CancellationToken cancellationToken) + => dashboardViewModelService.WatchProjectsAsync(initialList, cancellationToken); } diff --git a/src/Aspire.Dashboard/Components/Pages/ResourceLogsBase.razor b/src/Aspire.Dashboard/Components/Pages/ResourceLogsBase.razor new file mode 100644 index 0000000000..e3635a6b0d --- /dev/null +++ b/src/Aspire.Dashboard/Components/Pages/ResourceLogsBase.razor @@ -0,0 +1,24 @@ +@namespace Aspire.Dashboard.Components.Pages +@using Aspire.Dashboard.Model +@typeparam TResource where TResource : ResourceViewModel + +

ResourceLogsBase

+ +Microsoft.Aspire @ResourceType Logs + +

@ResourceType Logs

+ +
+ + + + @_status + + + +
diff --git a/src/Aspire.Dashboard/Components/Pages/ResourceLogsBase.razor.cs b/src/Aspire.Dashboard/Components/Pages/ResourceLogsBase.razor.cs new file mode 100644 index 0000000000..85b62db611 --- /dev/null +++ b/src/Aspire.Dashboard/Components/Pages/ResourceLogsBase.razor.cs @@ -0,0 +1,204 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Aspire.Dashboard.Model; +using Microsoft.AspNetCore.Components; +using Microsoft.JSInterop; + +namespace Aspire.Dashboard.Components.Pages; + +public abstract partial class ResourceLogsBase : ComponentBase, IAsyncDisposable + where TResource : ResourceViewModel +{ + [Inject] + public required IDashboardViewModelService DashboardViewModelService { get; init; } + [Inject] + public required IJSRuntime JS { get; init; } + [Inject] + public required NavigationManager NavigationManager { get; init; } + + protected abstract string? ResourceName { get; } + protected abstract string ResourceType { get; } + protected abstract string LoadingResourcesMessage { get; } + protected abstract string NoResourceSelectedMessage { get; } + protected abstract string LogsNotAvailableMessage { get; } + protected abstract string UrlPrefix { get; } + + protected abstract Task> GetResources(IDashboardViewModelService dashboardViewModelService); + protected abstract IAsyncEnumerable> WatchResources( + IDashboardViewModelService dashboardViewModelService, + IEnumerable? initialList, + CancellationToken cancellationToken); + + private TResource? _selectedResource; + private readonly Dictionary _resourceNameMapping = new(); + private IEnumerable Resources => _resourceNameMapping.Select(kvp => kvp.Value).OrderBy(c => c.Name); + private LogViewer? _logViewer; + private readonly CancellationTokenSource _watchContainersTokenSource = new(); + private CancellationTokenSource? _watchLogsTokenSource; + private string _status = LogStatus.Initializing; + +#pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task + protected override async Task OnInitializedAsync() + { + _status = LoadingResourcesMessage; + + var initialList = await GetResources(DashboardViewModelService); + + foreach (var result in initialList) + { + _resourceNameMapping[result.Name] = result; + } + + if (ResourceName is not null) + { + _selectedResource = initialList?.FirstOrDefault(c => string.Equals(ResourceName, c.Name, StringComparison.Ordinal)); + } + else if (initialList?.Count > 0) + { + _selectedResource = initialList[0]; + } + + await LoadLogsAsync(); + + _ = Task.Run(async () => + { + await foreach (var componentChanged in WatchResources(DashboardViewModelService, initialList?.Select(t => t.NamespacedName), _watchContainersTokenSource.Token)) + { + await OnResourceListChangedAsync(componentChanged.ObjectChangeType, componentChanged.Component); + } + }); + } + + private Task ClearLogsAsync() + => _logViewer is not null ? _logViewer.ClearLogsAsync() : Task.CompletedTask; + + private async ValueTask LoadLogsAsync() + { + if (_selectedResource is null) + { + _status = NoResourceSelectedMessage; + } + else if (_logViewer is null) + { + _status = LogStatus.InitializingLogViewer; + } + else + { + _watchLogsTokenSource = new CancellationTokenSource(); + if (await _selectedResource.LogSource.StartAsync(_watchLogsTokenSource.Token)) + { + _ = Task.Run(async () => + { + await _logViewer.WatchLogsAsync(() => _selectedResource.LogSource.WatchOutputLogAsync(_watchLogsTokenSource.Token), LogEntryType.Default); + }); + + _ = Task.Run(async () => + { + await _logViewer.WatchLogsAsync(() => _selectedResource.LogSource.WatchErrorLogAsync(_watchLogsTokenSource.Token), LogEntryType.Error); + }); + + _status = LogStatus.WatchingLogs; + } + else + { + _watchLogsTokenSource = null; + _status = LogsNotAvailableMessage; + } + } + } + + private async Task HandleSelectedOptionChangedAsync() + { + if (_selectedResource is not null) + { + // Change the URL + NavigationManager.NavigateTo($"{UrlPrefix}/{_selectedResource.Name}"); + await StopWatchingLogsAsync(); + await ClearLogsAsync(); + await LoadLogsAsync(); + } + } + + private async Task OnResourceListChangedAsync(ObjectChangeType changeType, TResource resourceViewModel) + { + if (changeType == ObjectChangeType.Added) + { + _resourceNameMapping[resourceViewModel.Name] = resourceViewModel; + + if (_selectedResource is null) + { + if (string.IsNullOrEmpty(ResourceName) || string.Equals(ResourceName, resourceViewModel.Name, StringComparison.Ordinal)) + { + _selectedResource = resourceViewModel; + await LoadLogsAsync(); + } + } + } + else if (changeType == ObjectChangeType.Modified) + { + _resourceNameMapping[resourceViewModel.Name] = resourceViewModel; + if (string.Equals(_selectedResource?.Name, resourceViewModel.Name, StringComparison.Ordinal)) + { + _selectedResource = resourceViewModel; + + if (_watchLogsTokenSource is null) + { + await LoadLogsAsync(); + } + } + } + else if (changeType == ObjectChangeType.Deleted) + { + _resourceNameMapping.Remove(resourceViewModel.Name); + if (string.Equals(_selectedResource?.Name, resourceViewModel.Name, StringComparison.Ordinal)) + { + if (_resourceNameMapping.Count > 0) + { + _selectedResource = Resources.First(); + await HandleSelectedOptionChangedAsync(); + } + } + } + + await InvokeAsync(StateHasChanged); + } + + private static string GetDisplayText(TResource resource) + { + var stateText = ""; + if (string.IsNullOrEmpty(resource.State)) + { + stateText = " (Unknown State)"; + } + else if (resource.State != "Running") + { + stateText = $" ({resource.State})"; + } + return $"{resource.Name}{stateText}"; + } + + public async ValueTask DisposeAsync() + { + await DisposeWatchContainersTokenSource(); + await StopWatchingLogsAsync(); + } + + private async Task DisposeWatchContainersTokenSource() + { + await _watchContainersTokenSource.CancelAsync(); + _watchContainersTokenSource.Dispose(); + } + + private async Task StopWatchingLogsAsync() + { + if (_watchLogsTokenSource is not null) + { + await _watchLogsTokenSource.CancelAsync(); + _watchLogsTokenSource.Dispose(); + // The token source only gets created if selected resource is not null + await _selectedResource!.LogSource.StopAsync(); + _watchLogsTokenSource = null; + } + } +}