From e8ae2a6152f53a1bb20f6c84f4635743132218f5 Mon Sep 17 00:00:00 2001 From: rootdarkarchon Date: Sat, 23 Dec 2023 16:59:51 +0100 Subject: [PATCH] add events --- MareSynchronos/MarePlugin.cs | 2 + .../PlayerData/Handlers/PairHandler.cs | 22 ++ MareSynchronos/PlayerData/Pairs/Pair.cs | 2 +- .../PlayerData/Pairs/PairManager.cs | 4 +- MareSynchronos/Plugin.cs | 5 + MareSynchronos/Services/Events/Event.cs | 45 ++++ .../Services/Events/EventAggregator.cs | 112 +++++++++ .../Services/Events/EventSeverity.cs | 8 + MareSynchronos/Services/Mediator/Messages.cs | 2 + MareSynchronos/UI/CompactUI.cs | 11 +- MareSynchronos/UI/DataAnalysisUi.cs | 2 +- MareSynchronos/UI/EventViewerUI.cs | 223 ++++++++++++++++++ .../WebAPI/SignalR/ApiController.cs | 8 + 13 files changed, 442 insertions(+), 4 deletions(-) create mode 100644 MareSynchronos/Services/Events/Event.cs create mode 100644 MareSynchronos/Services/Events/EventAggregator.cs create mode 100644 MareSynchronos/Services/Events/EventSeverity.cs create mode 100644 MareSynchronos/UI/EventViewerUI.cs diff --git a/MareSynchronos/MarePlugin.cs b/MareSynchronos/MarePlugin.cs index dc7bb0b..4df6fc0 100644 --- a/MareSynchronos/MarePlugin.cs +++ b/MareSynchronos/MarePlugin.cs @@ -89,6 +89,8 @@ public class MarePlugin : MediatorSubscriberBase, IHostedService { var version = Assembly.GetExecutingAssembly().GetName().Version!; Logger.LogInformation("Launching {name} {major}.{minor}.{build}-lop{rev}", "Loporrit Sync", version.Major, version.Minor, version.Build, version.Revision); + Mediator.Publish(new EventMessage(new Services.Events.Event(nameof(MarePlugin), Services.Events.EventSeverity.Informational, + $"Starting Loporrit Sync {version.Major}.{version.Minor}.{version.Build}-lop{version.Revision}"))); Mediator.Subscribe(this, (msg) => _ = Task.Run(WaitForPlayerAndLaunchCharacterManager)); Mediator.Subscribe(this, (_) => DalamudUtilOnLogIn()); diff --git a/MareSynchronos/PlayerData/Handlers/PairHandler.cs b/MareSynchronos/PlayerData/Handlers/PairHandler.cs index 164a358..629ec88 100644 --- a/MareSynchronos/PlayerData/Handlers/PairHandler.cs +++ b/MareSynchronos/PlayerData/Handlers/PairHandler.cs @@ -5,6 +5,7 @@ using MareSynchronos.Interop; using MareSynchronos.PlayerData.Factories; using MareSynchronos.PlayerData.Pairs; using MareSynchronos.Services; +using MareSynchronos.Services.Events; using MareSynchronos.Services.Mediator; using MareSynchronos.Services.ServerConfiguration; using MareSynchronos.Utils; @@ -111,6 +112,9 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase if (_isVisible != value) { _isVisible = value; + string text = "User Visibility Changed, now: " + (_isVisible ? "Is Visible" : "Is not Visible"); + Mediator.Publish(new EventMessage(new Event(PlayerName, OnlineUser.User, nameof(PairHandler), + EventSeverity.Informational, text))); } } } @@ -126,6 +130,8 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase { if (_dalamudUtil.IsInCombat) { + Mediator.Publish(new EventMessage(new Event(PlayerName, OnlineUser.User, nameof(PairHandler), EventSeverity.Warning, + "Cannot apply character data: you are in combat, deferring application"))); Logger.LogDebug("[BASE-{appBase}] Received data but player is in combat", applicationBase); _dataReceivedInCombat = new(applicationBase, characterData, forceApplyCustomization); SetUploading(isUploading: false); @@ -134,6 +140,8 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase if (_charaHandler == null || (PlayerCharacter == IntPtr.Zero)) { + Mediator.Publish(new EventMessage(new Event(PlayerName, OnlineUser.User, nameof(PairHandler), EventSeverity.Warning, + "Cannot apply character data: Receiving Player is in an invalid state, deferring application"))); Logger.LogDebug("[BASE-{appBase}] Received data but player was in invalid state, charaHandlerIsNull: {charaIsNull}, playerPointerIsNull: {ptrIsNull}", applicationBase, _charaHandler == null, PlayerCharacter == IntPtr.Zero); var hasDiffMods = characterData.CheckUpdatedData(applicationBase, _cachedData, Logger, @@ -154,10 +162,15 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase if (_dalamudUtil.IsInCutscene || _dalamudUtil.IsInGpose || !_ipcManager.CheckPenumbraApi() || !_ipcManager.CheckGlamourerApi()) { + Mediator.Publish(new EventMessage(new Event(PlayerName, OnlineUser.User, nameof(PairHandler), EventSeverity.Warning, + "Cannot apply character data: you are in GPose, a Cutscene or Penumbra/Glamourer is not available"))); Logger.LogInformation("[BASE-{appbase}] Application of data for {player} while in cutscene/gpose or Penumbra/Glamourer unavailable, returning", applicationBase, this); return; } + Mediator.Publish(new EventMessage(new Event(PlayerName, OnlineUser.User, nameof(PairHandler), EventSeverity.Informational, + "Applying Character Data"))); + _forceApplyMods |= forceApplyCustomization; var charaDataToUpdate = characterData.CheckUpdatedData(applicationBase, _cachedData?.DeepClone() ?? new(), Logger, this, forceApplyCustomization, _forceApplyMods); @@ -217,6 +230,11 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase _charaHandler?.Dispose(); _charaHandler = null; + if (!string.IsNullOrEmpty(name)) + { + Mediator.Publish(new EventMessage(new Event(name, OnlineUser.User, nameof(PairHandler), EventSeverity.Informational, "Disposing User"))); + } + if (_lifetime.IsCancellationRequested) return; Logger.LogDebug("[{applicationId}] Removing Temp Collection for {name} ({user})", applicationId, name, OnlineUser); @@ -365,6 +383,8 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase Logger.LogDebug("[BASE-{appBase}] Downloading missing files for player {name}, {kind}", applicationBase, PlayerName, updatedData); if (toDownloadReplacements.Any()) { + Mediator.Publish(new EventMessage(new Event(PlayerName, OnlineUser.User, nameof(PairHandler), EventSeverity.Informational, + $"Starting download for {toDownloadReplacements.Count} files"))); await _downloadManager.DownloadFiles(_charaHandler!, toDownloadReplacements, downloadToken).ConfigureAwait(false); _downloadManager.CancelDownload(); } @@ -469,6 +489,8 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase Logger.LogDebug("One-Time Initializing {this}", this); Initialize(pc.Name); Logger.LogDebug("One-Time Initialized {this}", this); + Mediator.Publish(new EventMessage(new Event(PlayerName, OnlineUser.User, nameof(PairHandler), EventSeverity.Informational, + $"Initializing User For Character {pc.Name}"))); } if (_charaHandler?.Address != nint.Zero && !IsVisible) diff --git a/MareSynchronos/PlayerData/Pairs/Pair.cs b/MareSynchronos/PlayerData/Pairs/Pair.cs index f24cd60..c215361 100644 --- a/MareSynchronos/PlayerData/Pairs/Pair.cs +++ b/MareSynchronos/PlayerData/Pairs/Pair.cs @@ -152,7 +152,7 @@ public class Pair } CachedPlayer?.Dispose(); - CachedPlayer = _cachedPlayerFactory.Create(_onlineUserIdentDto!); + CachedPlayer = _cachedPlayerFactory.Create(new OnlineUserIdentDto(UserData, _onlineUserIdentDto!.Ident)); } finally { diff --git a/MareSynchronos/PlayerData/Pairs/PairManager.cs b/MareSynchronos/PlayerData/Pairs/PairManager.cs index 811deb0..9b12470 100644 --- a/MareSynchronos/PlayerData/Pairs/PairManager.cs +++ b/MareSynchronos/PlayerData/Pairs/PairManager.cs @@ -8,6 +8,7 @@ using MareSynchronos.API.Dto.Group; using MareSynchronos.API.Dto.User; using MareSynchronos.MareConfiguration; using MareSynchronos.PlayerData.Factories; +using MareSynchronos.Services.Events; using MareSynchronos.Services.Mediator; using Microsoft.Extensions.Logging; using System.Collections.Concurrent; @@ -138,8 +139,9 @@ public sealed class PairManager : DisposableMediatorSubscriberBase public void ReceiveCharaData(OnlineUserCharaDataDto dto) { - if (!_allClientPairs.ContainsKey(dto.User)) throw new InvalidOperationException("No user found for " + dto.User); + if (!_allClientPairs.TryGetValue(dto.User, out var pair)) throw new InvalidOperationException("No user found for " + dto.User); + Mediator.Publish(new EventMessage(new Event(pair.UserData, nameof(PairManager), EventSeverity.Informational, "Received Character Data"))); _allClientPairs[dto.User].ApplyData(dto); } diff --git a/MareSynchronos/Plugin.cs b/MareSynchronos/Plugin.cs index 9337715..48f962e 100644 --- a/MareSynchronos/Plugin.cs +++ b/MareSynchronos/Plugin.cs @@ -11,6 +11,7 @@ using MareSynchronos.PlayerData.Factories; using MareSynchronos.PlayerData.Pairs; using MareSynchronos.PlayerData.Services; using MareSynchronos.Services; +using MareSynchronos.Services.Events; using MareSynchronos.Services.Mediator; using MareSynchronos.Services.ServerConfiguration; using MareSynchronos.UI; @@ -91,6 +92,8 @@ public sealed class Plugin : IDalamudPlugin collection.AddSingleton(); collection.AddSingleton(); collection.AddSingleton(); + collection.AddSingleton((s) => new EventAggregator(pluginInterface.ConfigDirectory.FullName, + s.GetRequiredService>(), s.GetRequiredService())); collection.AddSingleton((s) => new DalamudUtilService(s.GetRequiredService>(), clientState, objectTable, framework, gameGui, toastGui, condition, gameData, targetManager, s.GetRequiredService(), s.GetRequiredService())); @@ -118,6 +121,7 @@ public sealed class Plugin : IDalamudPlugin collection.AddScoped(); collection.AddScoped(); collection.AddScoped(); + collection.AddScoped(); collection.AddScoped((s) => new EditProfileUi(s.GetRequiredService>(), s.GetRequiredService(), s.GetRequiredService(), pluginInterface.UiBuilder, s.GetRequiredService(), @@ -153,6 +157,7 @@ public sealed class Plugin : IDalamudPlugin collection.AddHostedService(p => p.GetRequiredService()); collection.AddHostedService(p => p.GetRequiredService()); collection.AddHostedService(p => p.GetRequiredService()); + collection.AddHostedService(p => p.GetRequiredService()); }) .Build() .RunAsync(_pluginCts.Token); diff --git a/MareSynchronos/Services/Events/Event.cs b/MareSynchronos/Services/Events/Event.cs new file mode 100644 index 0000000..3f5bead --- /dev/null +++ b/MareSynchronos/Services/Events/Event.cs @@ -0,0 +1,45 @@ +using MareSynchronos.API.Data; + +namespace MareSynchronos.Services.Events; + +public record Event +{ + public DateTime EventTime { get; } + public string UID { get; } + public string Character { get; } + public string EventSource { get; } + public EventSeverity EventSeverity { get; } + public string Message { get; } + + public Event(string? Character, UserData UserData, string EventSource, EventSeverity EventSeverity, string Message) + { + EventTime = DateTime.Now; + this.UID = UserData.AliasOrUID; + this.Character = Character ?? string.Empty; + this.EventSource = EventSource; + this.EventSeverity = EventSeverity; + this.Message = Message; + } + + public Event(UserData UserData, string EventSource, EventSeverity EventSeverity, string Message) : this(null, UserData, EventSource, EventSeverity, Message) + { + } + + public Event(string EventSource, EventSeverity EventSeverity, string Message) + : this(new UserData(string.Empty), EventSource, EventSeverity, Message) + { + } + + public override string ToString() + { + if (string.IsNullOrEmpty(UID)) + return $"{EventTime:HH:mm:ss.fff}\t[{EventSource}]{{{(int)EventSeverity}}}\t{Message}"; + else + { + if (string.IsNullOrEmpty(Character)) + return $"{EventTime:HH:mm:ss.fff}\t[{EventSource}]{{{(int)EventSeverity}}}\t<{UID}> {Message}"; + else + return $"{EventTime:HH:mm:ss.fff}\t[{EventSource}]{{{(int)EventSeverity}}}\t<{UID}\\{Character}> {Message}"; + } + } +} \ No newline at end of file diff --git a/MareSynchronos/Services/Events/EventAggregator.cs b/MareSynchronos/Services/Events/EventAggregator.cs new file mode 100644 index 0000000..c8c35e5 --- /dev/null +++ b/MareSynchronos/Services/Events/EventAggregator.cs @@ -0,0 +1,112 @@ +using MareSynchronos.Services.Mediator; +using MareSynchronos.Utils; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace MareSynchronos.Services.Events; + +public class EventAggregator : MediatorSubscriberBase, IHostedService +{ + private readonly RollingList _events = new(500); + private readonly SemaphoreSlim _lock = new(1); + private readonly string _configDirectory; + private readonly ILogger _logger; + + public Lazy> EventList { get; private set; } + public bool NewEventsAvailable => !EventList.IsValueCreated; + public string EventLogFolder => Path.Combine(_configDirectory, "eventlog"); + private string CurrentLogName => $"{DateTime.Now:yyyy-MM-dd}-events.log"; + private DateTime _currentTime; + + public EventAggregator(string configDirectory, ILogger logger, MareMediator mareMediator) : base(logger, mareMediator) + { + Logger.LogInformation("Starting EventAggregatorService"); + Logger.LogInformation("Started EventAggregatorService"); + + Mediator.Subscribe(this, (msg) => + { + _lock.Wait(); + try + { + Logger.LogTrace("Received Event: {evt}", msg.Event.ToString()); + _events.Add(msg.Event); + WriteToFile(msg.Event); + } + finally + { + _lock.Release(); + } + + RecreateLazy(); + }); + + EventList = CreateEventLazy(); + _configDirectory = configDirectory; + _logger = logger; + _currentTime = DateTime.Now - TimeSpan.FromDays(1); + } + + private void RecreateLazy() + { + if (!EventList.IsValueCreated) return; + + EventList = CreateEventLazy(); + } + + private Lazy> CreateEventLazy() + { + return new Lazy>(() => + { + _lock.Wait(); + try + { + return [.. _events]; + } + finally + { + _lock.Release(); + } + }); + } + + private void WriteToFile(Event receivedEvent) + { + if (DateTime.Now.Day != _currentTime.Day) + { + try + { + _currentTime = DateTime.Now; + var filesInDirectory = Directory.EnumerateFiles(EventLogFolder, "*.log"); + if (filesInDirectory.Skip(10).Any()) + { + File.Delete(filesInDirectory.OrderBy(f => new FileInfo(f).LastWriteTimeUtc).First()); + } + } + catch (Exception ex) + { + _logger.LogWarning(ex, "Could not delete last events"); + } + } + + var eventLogFile = Path.Combine(EventLogFolder, CurrentLogName); + try + { + if (!Directory.Exists(EventLogFolder)) Directory.CreateDirectory(EventLogFolder); + File.AppendAllLines(eventLogFile, [receivedEvent.ToString()]); + } + catch (Exception ex) + { + _logger.LogWarning(ex, $"Could not write to event file {eventLogFile}"); + } + } + + public Task StartAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } + + public Task StopAsync(CancellationToken cancellationToken) + { + return Task.CompletedTask; + } +} diff --git a/MareSynchronos/Services/Events/EventSeverity.cs b/MareSynchronos/Services/Events/EventSeverity.cs new file mode 100644 index 0000000..aafb0cf --- /dev/null +++ b/MareSynchronos/Services/Events/EventSeverity.cs @@ -0,0 +1,8 @@ +namespace MareSynchronos.Services.Events; + +public enum EventSeverity +{ + Informational = 0, + Warning = 1, + Error = 2 +} diff --git a/MareSynchronos/Services/Mediator/Messages.cs b/MareSynchronos/Services/Mediator/Messages.cs index 520995c..6cd83d1 100644 --- a/MareSynchronos/Services/Mediator/Messages.cs +++ b/MareSynchronos/Services/Mediator/Messages.cs @@ -4,6 +4,7 @@ using MareSynchronos.API.Dto; using MareSynchronos.API.Dto.Group; using MareSynchronos.PlayerData.Handlers; using MareSynchronos.PlayerData.Pairs; +using MareSynchronos.Services.Events; using MareSynchronos.WebAPI.Files.Models; using System.Numerics; @@ -78,6 +79,7 @@ public record CensusUpdateMessage(byte Gender, byte RaceId, byte TribeId) : Mess public record TargetPairMessage(Pair Pair) : MessageBase; public record CombatStartMessage : MessageBase; public record CombatEndMessage : MessageBase; +public record EventMessage(Event Event) : MessageBase; public record PenumbraDirectoryChangedMessage(string? ModDirectory) : MessageBase; public record UserChatMsgMessage(SignedChatMessage ChatMsg) : MessageBase; diff --git a/MareSynchronos/UI/CompactUI.cs b/MareSynchronos/UI/CompactUI.cs index 7e79632..939ef4b 100644 --- a/MareSynchronos/UI/CompactUI.cs +++ b/MareSynchronos/UI/CompactUI.cs @@ -502,12 +502,21 @@ public class CompactUi : WindowMediatorSubscriberBase ImGui.TextUnformatted(downloadText); } - if (UiSharedService.NormalizedIconTextButton(FontAwesomeIcon.PersonCircleQuestion, "Mare Character Data Analysis", WindowContentWidth)) + var bottomButtonWidth = (WindowContentWidth - ImGui.GetStyle().ItemSpacing.X) / 2; + + if (UiSharedService.NormalizedIconTextButton(FontAwesomeIcon.PersonCircleQuestion, "Character Analysis", bottomButtonWidth)) { Mediator.Publish(new UiToggleMessage(typeof(DataAnalysisUi))); } ImGui.SameLine(); + + if (UiSharedService.NormalizedIconTextButton(FontAwesomeIcon.NotesMedical, "Event Viewer", bottomButtonWidth)) + { + Mediator.Publish(new UiToggleMessage(typeof(EventViewerUI))); + } + + ImGui.SameLine(); } private void DrawUIDHeader() diff --git a/MareSynchronos/UI/DataAnalysisUi.cs b/MareSynchronos/UI/DataAnalysisUi.cs index 183f2d0..0b84084 100644 --- a/MareSynchronos/UI/DataAnalysisUi.cs +++ b/MareSynchronos/UI/DataAnalysisUi.cs @@ -32,7 +32,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase private bool _showModal = false; public DataAnalysisUi(ILogger logger, MareMediator mediator, CharacterAnalyzer characterAnalyzer, IpcManager ipcManager, PerformanceCollectorService performanceCollectorService) - : base(logger, mediator, "Mare Character Data Analysis", performanceCollectorService) + : base(logger, mediator, "Character Data Analysis", performanceCollectorService) { _characterAnalyzer = characterAnalyzer; _ipcManager = ipcManager; diff --git a/MareSynchronos/UI/EventViewerUI.cs b/MareSynchronos/UI/EventViewerUI.cs new file mode 100644 index 0000000..023be7b --- /dev/null +++ b/MareSynchronos/UI/EventViewerUI.cs @@ -0,0 +1,223 @@ +using Dalamud.Interface; +using Dalamud.Interface.Colors; +using Dalamud.Interface.Utility; +using Dalamud.Interface.Utility.Raii; +using ImGuiNET; +using MareSynchronos.Services; +using MareSynchronos.Services.Events; +using MareSynchronos.Services.Mediator; +using Microsoft.Extensions.Logging; +using System.Diagnostics; +using System.Globalization; +using System.Numerics; + +namespace MareSynchronos.UI; + +internal class EventViewerUI : WindowMediatorSubscriberBase +{ + private readonly EventAggregator _eventAggregator; + private readonly UiSharedService _uiSharedService; + private List _currentEvents = new(); + private Lazy> _filteredEvents; + private string _filterFreeText = string.Empty; + private string _filterCharacter = string.Empty; + private string _filterUid = string.Empty; + private string _filterSource = string.Empty; + private string _filterEvent = string.Empty; + + private List CurrentEvents + { + get + { + return _currentEvents; + } + set + { + _currentEvents = value; + _filteredEvents = RecreateFilter(); + } + } + + public EventViewerUI(ILogger logger, MareMediator mediator, + EventAggregator eventAggregator, UiSharedService uiSharedService, + PerformanceCollectorService performanceCollectorService) : base(logger, mediator, "Event Viewer", performanceCollectorService) + { + _eventAggregator = eventAggregator; + _uiSharedService = uiSharedService; + SizeConstraints = new() + { + MinimumSize = new(600, 500), + MaximumSize = new(1000, 2000) + }; + _filteredEvents = RecreateFilter(); + } + + private Lazy> RecreateFilter() + { + return new(() => + CurrentEvents.Where(f => + (string.IsNullOrEmpty(_filterFreeText) + || (f.EventSource.Contains(_filterFreeText, StringComparison.OrdinalIgnoreCase) + || f.Character.Contains(_filterFreeText, StringComparison.OrdinalIgnoreCase) + || f.UID.Contains(_filterFreeText, StringComparison.OrdinalIgnoreCase) + || f.Message.Contains(_filterFreeText, StringComparison.OrdinalIgnoreCase) + )) + && + (string.IsNullOrEmpty(_filterUid) + || (f.UID.Contains(_filterUid, StringComparison.OrdinalIgnoreCase)) + ) + && + (string.IsNullOrEmpty(_filterSource) + || (f.EventSource.Contains(_filterSource, StringComparison.OrdinalIgnoreCase)) + ) + && + (string.IsNullOrEmpty(_filterCharacter) + || (f.Character.Contains(_filterCharacter, StringComparison.OrdinalIgnoreCase)) + ) + && + (string.IsNullOrEmpty(_filterEvent) + || (f.Message.Contains(_filterEvent, StringComparison.OrdinalIgnoreCase)) + ) + ).ToList()); + } + + private void ClearFilters() + { + _filterFreeText = string.Empty; + _filterCharacter = string.Empty; + _filterUid = string.Empty; + _filterSource = string.Empty; + _filterEvent = string.Empty; + _filteredEvents = RecreateFilter(); + } + + public override void OnOpen() + { + CurrentEvents = _eventAggregator.EventList.Value.OrderByDescending(f => f.EventTime).ToList(); + ClearFilters(); + } + + protected override void DrawInternal() + { + using (ImRaii.Disabled(!_eventAggregator.NewEventsAvailable)) + { + if (UiSharedService.NormalizedIconTextButton(FontAwesomeIcon.ArrowsToCircle, "Refresh events")) + { + CurrentEvents = _eventAggregator.EventList.Value.OrderByDescending(f => f.EventTime).ToList(); + } + } + + if (_eventAggregator.NewEventsAvailable) + { + ImGui.SameLine(); + ImGui.AlignTextToFramePadding(); + UiSharedService.ColorTextWrapped("New events are available, press refresh to update", ImGuiColors.DalamudYellow); + } + + var buttonSize = UiSharedService.GetNormalizedIconTextButtonSize(FontAwesomeIcon.FolderOpen, "Open EventLog Folder"); + var dist = ImGui.GetWindowContentRegionMax().X - buttonSize.X; + ImGui.SameLine(dist); + if (UiSharedService.NormalizedIconTextButton(FontAwesomeIcon.FolderOpen, "Open EventLog folder")) + { + ProcessStartInfo ps = new() + { + FileName = _eventAggregator.EventLogFolder, + UseShellExecute = true, + WindowStyle = ProcessWindowStyle.Normal + }; + Process.Start(ps); + } + + UiSharedService.FontText("Last Events", _uiSharedService.UidFont); + var foldOut = ImRaii.TreeNode("Filter"); + if (foldOut) + { + if (UiSharedService.NormalizedIconTextButton(FontAwesomeIcon.Ban, "Clear Filters")) + { + ClearFilters(); + } + bool changedFilter = false; + ImGui.SetNextItemWidth(200); + changedFilter |= ImGui.InputText("Search all columns", ref _filterFreeText, 50); + ImGui.SetNextItemWidth(200); + changedFilter |= ImGui.InputText("Filter by Source", ref _filterSource, 50); + ImGui.SetNextItemWidth(200); + changedFilter |= ImGui.InputText("Filter by UID", ref _filterUid, 50); + ImGui.SetNextItemWidth(200); + changedFilter |= ImGui.InputText("Filter by Character", ref _filterCharacter, 50); + ImGui.SetNextItemWidth(200); + changedFilter |= ImGui.InputText("Filter by Event", ref _filterEvent, 50); + if (changedFilter) _filteredEvents = RecreateFilter(); + } + foldOut.Dispose(); + + var cursorPos = ImGui.GetCursorPosY(); + var max = ImGui.GetWindowContentRegionMax(); + var min = ImGui.GetWindowContentRegionMin(); + var width = max.X - min.X; + var height = max.Y - cursorPos; + using var table = ImRaii.Table("eventTable", 6, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.ScrollY | ImGuiTableFlags.RowBg, + new Vector2(width, height)); + if (table) + { + ImGui.TableSetupScrollFreeze(0, 1); + ImGui.TableSetupColumn(string.Empty, ImGuiTableColumnFlags.NoSort); + ImGui.TableSetupColumn("Time"); + ImGui.TableSetupColumn("Source"); + ImGui.TableSetupColumn("UID"); + ImGui.TableSetupColumn("Character"); + ImGui.TableSetupColumn("Event"); + ImGui.TableHeadersRow(); + foreach (var ev in _filteredEvents.Value) + { + var icon = ev.EventSeverity switch + { + EventSeverity.Informational => FontAwesomeIcon.InfoCircle, + EventSeverity.Warning => FontAwesomeIcon.ExclamationTriangle, + EventSeverity.Error => FontAwesomeIcon.Cross, + _ => FontAwesomeIcon.QuestionCircle + }; + + var iconColor = ev.EventSeverity switch + { + EventSeverity.Informational => new Vector4(), + EventSeverity.Warning => ImGuiColors.DalamudYellow, + EventSeverity.Error => ImGuiColors.DalamudRed, + _ => new Vector4() + }; + + ImGui.TableNextColumn(); + UiSharedService.NormalizedIcon(icon, iconColor == new Vector4() ? null : iconColor); + UiSharedService.AttachToolTip(ev.EventSeverity.ToString()); + ImGui.TableNextColumn(); + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted(ev.EventTime.ToString("G", CultureInfo.CurrentCulture)); + ImGui.TableNextColumn(); + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted(ev.EventSource); + ImGui.TableNextColumn(); + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted(string.IsNullOrEmpty(ev.UID) ? "--" : ev.UID); + ImGui.TableNextColumn(); + ImGui.AlignTextToFramePadding(); + ImGui.TextUnformatted(string.IsNullOrEmpty(ev.Character) ? "--" : ev.Character); + ImGui.TableNextColumn(); + ImGui.AlignTextToFramePadding(); + var posX = ImGui.GetCursorPosX(); + var maxTextLength = ImGui.GetWindowContentRegionMax().X - posX; + var textSize = ImGui.CalcTextSize(ev.Message).X; + var msg = ev.Message; + while (textSize > maxTextLength) + { + msg = msg[..^5] + "..."; + textSize = ImGui.CalcTextSize(msg).X; + } + ImGui.TextUnformatted(msg); + if (!string.Equals(msg, ev.Message, StringComparison.Ordinal)) + { + UiSharedService.AttachToolTip(ev.Message); + } + } + } + } +} diff --git a/MareSynchronos/WebAPI/SignalR/ApiController.cs b/MareSynchronos/WebAPI/SignalR/ApiController.cs index a0a02a8..eea2cd4 100644 --- a/MareSynchronos/WebAPI/SignalR/ApiController.cs +++ b/MareSynchronos/WebAPI/SignalR/ApiController.cs @@ -128,6 +128,8 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM await StopConnection(ServerState.Disconnected).ConfigureAwait(false); Logger.LogInformation("Recreating Connection"); + Mediator.Publish(new EventMessage(new Services.Events.Event(nameof(ApiController), Services.Events.EventSeverity.Informational, + $"Starting Connection to {_serverManager.CurrentServer.ServerName}"))); _connectionCancellationTokenSource.Cancel(); _connectionCancellationTokenSource = new CancellationTokenSource(); @@ -411,6 +413,9 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM _healthCheckTokenSource?.Cancel(); ServerState = ServerState.Reconnecting; Logger.LogWarning(arg, "Connection closed... Reconnecting"); + Mediator.Publish(new EventMessage(new Services.Events.Event(nameof(ApiController), Services.Events.EventSeverity.Warning, + $"Connection interrupted, reconnecting to {_serverManager.CurrentServer.ServerName}"))); + } private async Task StopConnection(ServerState state) @@ -422,6 +427,9 @@ public sealed partial class ApiController : DisposableMediatorSubscriberBase, IM if (_mareHub is not null) { + Mediator.Publish(new EventMessage(new Services.Events.Event(nameof(ApiController), Services.Events.EventSeverity.Informational, + $"Stopping existing connection to {_serverManager.CurrentServer.ServerName}"))); + _initialized = false; _healthCheckTokenSource?.Cancel(); Mediator.Publish(new DisconnectedMessage());