diff --git a/src/EventLogExpert.Eventing.Tests/Helpers/LogNamesTests.cs b/src/EventLogExpert.Eventing.Tests/Helpers/LogNamesTests.cs
new file mode 100644
index 00000000..ff058a3e
--- /dev/null
+++ b/src/EventLogExpert.Eventing.Tests/Helpers/LogNamesTests.cs
@@ -0,0 +1,36 @@
+// // Copyright (c) Microsoft Corporation.
+// // Licensed under the MIT License.
+
+using EventLogExpert.Eventing.Helpers;
+
+namespace EventLogExpert.Eventing.Tests.Helpers;
+
+public sealed class LogNamesTests
+{
+ [Fact]
+ public void AdminOnlyLiveLogNames_ContainsExpectedNames()
+ {
+ Assert.Contains(LogNames.SecurityLog, LogNames.AdminOnlyLiveLogNames);
+ Assert.Contains(LogNames.StateLog, LogNames.AdminOnlyLiveLogNames);
+ Assert.Equal(2, LogNames.AdminOnlyLiveLogNames.Count);
+ }
+
+ [Theory]
+ [InlineData("security")]
+ [InlineData("SECURITY")]
+ [InlineData("state")]
+ [InlineData("STATE")]
+ public void AdminOnlyLiveLogNames_ShouldMatchCaseInsensitively(string input)
+ {
+ Assert.Contains(input, LogNames.AdminOnlyLiveLogNames);
+ }
+
+ [Fact]
+ public void Constants_HaveExpectedValues()
+ {
+ Assert.Equal("Application", LogNames.ApplicationLog);
+ Assert.Equal("Security", LogNames.SecurityLog);
+ Assert.Equal("State", LogNames.StateLog);
+ Assert.Equal("System", LogNames.SystemLog);
+ }
+}
diff --git a/src/EventLogExpert.Eventing/Helpers/LogNames.cs b/src/EventLogExpert.Eventing/Helpers/LogNames.cs
new file mode 100644
index 00000000..74996b29
--- /dev/null
+++ b/src/EventLogExpert.Eventing/Helpers/LogNames.cs
@@ -0,0 +1,19 @@
+// // Copyright (c) Microsoft Corporation.
+// // Licensed under the MIT License.
+
+namespace EventLogExpert.Eventing.Helpers;
+
+public static class LogNames
+{
+ public const string ApplicationLog = "Application";
+ public const string SecurityLog = "Security";
+ public const string StateLog = "State";
+ public const string SystemLog = "System";
+
+ /// Live event log names that require process elevation to read.
+ public static IReadOnlySet AdminOnlyLiveLogNames { get; } = new HashSet(StringComparer.OrdinalIgnoreCase)
+ {
+ SecurityLog,
+ StateLog,
+ };
+}
diff --git a/src/EventLogExpert.Eventing/Providers/RegistryProvider.cs b/src/EventLogExpert.Eventing/Providers/RegistryProvider.cs
index 21cc0d6d..bebc81e4 100644
--- a/src/EventLogExpert.Eventing/Providers/RegistryProvider.cs
+++ b/src/EventLogExpert.Eventing/Providers/RegistryProvider.cs
@@ -34,7 +34,7 @@ public IEnumerable GetMessageFilesForLegacyProvider(string providerName)
foreach (var logSubKeyName in eventLogKey.GetSubKeyNames())
{
// Skip Security and State since it requires elevation
- if (logSubKeyName is "Security" or "State")
+ if (LogNames.AdminOnlyLiveLogNames.Contains(logSubKeyName))
{
continue;
}
diff --git a/src/EventLogExpert.UI.Tests/LogNameMethodsTests.cs b/src/EventLogExpert.UI.Tests/LogNameMethodsTests.cs
index d480e087..b46eaf0f 100644
--- a/src/EventLogExpert.UI.Tests/LogNameMethodsTests.cs
+++ b/src/EventLogExpert.UI.Tests/LogNameMethodsTests.cs
@@ -119,4 +119,22 @@ public void GetMenuPath_WhenSimpleRootLog_ShouldReturnSingleSegment()
Assert.Equal(["Application"], path);
}
+
+ [Fact]
+ public void HardCodedLiveLogNames_ContainsExpectedNames()
+ {
+ Assert.Contains("Application", LogNameMethods.HardCodedLiveLogNames);
+ Assert.Contains("System", LogNameMethods.HardCodedLiveLogNames);
+ Assert.Contains("Security", LogNameMethods.HardCodedLiveLogNames);
+ Assert.Equal(3, LogNameMethods.HardCodedLiveLogNames.Count);
+ }
+
+ [Theory]
+ [InlineData("application")]
+ [InlineData("APPLICATION")]
+ [InlineData("Application")]
+ public void HardCodedLiveLogNames_ShouldMatchCaseInsensitively(string input)
+ {
+ Assert.Contains(input, LogNameMethods.HardCodedLiveLogNames);
+ }
}
diff --git a/src/EventLogExpert.UI/Interfaces/IMenuActionService.cs b/src/EventLogExpert.UI/Interfaces/IMenuActionService.cs
index ee1ad2ce..9c35162a 100644
--- a/src/EventLogExpert.UI/Interfaces/IMenuActionService.cs
+++ b/src/EventLogExpert.UI/Interfaces/IMenuActionService.cs
@@ -30,13 +30,13 @@ public interface IMenuActionService
Task OpenDocsAsync();
- Task OpenFileAsync(bool addLog);
+ Task OpenFileAsync(bool combineLog);
- Task OpenFolderAsync(bool addLog);
+ Task OpenFolderAsync(bool combineLog);
Task OpenIssueAsync();
- Task OpenLiveLogAsync(string logName, bool addLog);
+ Task OpenLiveLogAsync(string logName, bool combineLog);
Task OpenSettingsAsync();
diff --git a/src/EventLogExpert.UI/LogNameMethods.cs b/src/EventLogExpert.UI/LogNameMethods.cs
index 1c57ef93..02e6524c 100644
--- a/src/EventLogExpert.UI/LogNameMethods.cs
+++ b/src/EventLogExpert.UI/LogNameMethods.cs
@@ -1,12 +1,22 @@
// // Copyright (c) Microsoft Corporation.
// // Licensed under the MIT License.
+using EventLogExpert.Eventing.Helpers;
+
namespace EventLogExpert.UI;
public static class LogNameMethods
{
private const string MicrosoftWindowsPrefix = "Microsoft-Windows-";
+ /// Live event log names hard-coded in MenuBar's File menu — filter these from dynamic log enumeration to avoid duplicates.
+ public static IReadOnlySet HardCodedLiveLogNames { get; } = new HashSet(StringComparer.OrdinalIgnoreCase)
+ {
+ LogNames.ApplicationLog,
+ LogNames.SystemLog,
+ LogNames.SecurityLog,
+ };
+
public static IReadOnlyList GetMenuPath(string logName)
{
if (string.IsNullOrWhiteSpace(logName))
diff --git a/src/EventLogExpert/Services/MauiMenuActionService.cs b/src/EventLogExpert/Services/MauiMenuActionService.cs
index 18162b7c..e000e329 100644
--- a/src/EventLogExpert/Services/MauiMenuActionService.cs
+++ b/src/EventLogExpert/Services/MauiMenuActionService.cs
@@ -114,6 +114,7 @@ public async Task> GetOtherLogNamesAsync()
_cachedLogNames = await Task.Run>(() =>
EventLogSession.GlobalSession.GetLogNames()
+ .Where(name => !LogNameMethods.HardCodedLiveLogNames.Contains(name))
.OrderBy(name => name, StringComparer.OrdinalIgnoreCase)
.ToList());
@@ -130,7 +131,7 @@ public async Task> GetOtherLogNamesAsync()
public Task OpenDocsAsync() =>
OpenBrowserAsync("https://github.com/microsoft/EventLogExpert/blob/main/docs/Home.md");
- public async Task OpenFileAsync(bool addLog)
+ public async Task OpenFileAsync(bool combineLog)
{
var options = new PickOptions
{
@@ -142,7 +143,7 @@ public async Task OpenFileAsync(bool addLog)
if (!files.Any()) { return; }
- if (!addLog)
+ if (!combineLog)
{
await CloseAllLogsAsync();
}
@@ -155,7 +156,7 @@ public async Task OpenFileAsync(bool addLog)
}
}
- public async Task OpenFolderAsync(bool addLog)
+ public async Task OpenFolderAsync(bool combineLog)
{
string? folderPath = await FolderPickerHelper.PickFolderAsync();
@@ -165,7 +166,7 @@ public async Task OpenFolderAsync(bool addLog)
if (files.Count == 0) { return; }
- if (!addLog)
+ if (!combineLog)
{
await CloseAllLogsAsync();
}
@@ -178,13 +179,13 @@ public async Task OpenFolderAsync(bool addLog)
public Task OpenIssueAsync() => OpenBrowserAsync("https://github.com/microsoft/EventLogExpert/issues/new");
- public Task OpenLiveLogAsync(string logName, bool addLog) => OpenLogAsync(logName, PathType.LogName, addLog);
+ public Task OpenLiveLogAsync(string logName, bool combineLog) => OpenLogAsync(logName, PathType.LogName, combineLog);
- public async Task OpenLogAsync(string logPath, PathType pathType, bool shouldAddLog = false)
+ public async Task OpenLogAsync(string logPath, PathType pathType, bool combineLog = false)
{
if (string.IsNullOrWhiteSpace(logPath)) { return; }
- if (shouldAddLog && _eventLogState.Value.ActiveLogs.ContainsKey(logPath)) { return; }
+ if (combineLog && _eventLogState.Value.ActiveLogs.ContainsKey(logPath)) { return; }
EventLogInformation? eventLogInformation;
@@ -214,7 +215,7 @@ await _dialogService.ShowAlert(
return;
}
- if (!shouldAddLog)
+ if (!combineLog)
{
await _cancellationTokenSource.CancelAsync();
_dispatcher.Dispatch(new EventLogAction.CloseAll());
diff --git a/src/EventLogExpert/Shared/Components/Menu/MenuBar.razor.cs b/src/EventLogExpert/Shared/Components/Menu/MenuBar.razor.cs
index e5eeb1ca..fffb5835 100644
--- a/src/EventLogExpert/Shared/Components/Menu/MenuBar.razor.cs
+++ b/src/EventLogExpert/Shared/Components/Menu/MenuBar.razor.cs
@@ -1,8 +1,10 @@
// // Copyright (c) Microsoft Corporation.
// // Licensed under the MIT License.
+using EventLogExpert.Eventing.Helpers;
using EventLogExpert.UI;
using EventLogExpert.UI.Interfaces;
+using EventLogExpert.UI.Services;
using EventLogExpert.UI.Store.EventLog;
using EventLogExpert.UI.Store.FilterPane;
using Fluxor;
@@ -16,6 +18,7 @@ namespace EventLogExpert.Shared.Components.Menu;
public sealed partial class MenuBar : IDisposable
{
private readonly List _bars = [];
+
private ElementReference[] _barElements = [];
private int _focusedBarIndex;
private long _openRequestId;
@@ -27,9 +30,14 @@ public sealed partial class MenuBar : IDisposable
[Inject]
private IStateSelection ContinuouslyUpdate { get; init; } = null!;
+ [Inject] private ICurrentVersionProvider CurrentVersionProvider { get; init; } = null!;
+
[Inject]
private IStateSelection FilterPaneIsEnabled { get; init; } = null!;
+ [Inject]
+ private IStateSelection HasActiveLogs { get; init; } = null!;
+
[Inject] private IJSRuntime JSRuntime { get; init; } = null!;
[Inject] private IMenuService MenuService { get; init; } = null!;
@@ -49,6 +57,7 @@ protected override void OnInitialized()
{
ContinuouslyUpdate.Select(state => state.ContinuouslyUpdate);
FilterPaneIsEnabled.Select(state => state.IsEnabled);
+ HasActiveLogs.Select(state => !state.ActiveLogs.IsEmpty);
Settings.CopyTypeChanged += OnSettingsChanged;
MenuService.StateChanged += OnMenuServiceStateChanged;
@@ -86,14 +95,19 @@ private IReadOnlyList