using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.IO; using System.Runtime.CompilerServices; using System.Text.RegularExpressions; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Input; using System.Windows.Media; using com.sun.org.glassfish.gmbal; using FastReport.DataVisualization.Charting; using Microsoft.Win32; using NPOI.XWPF.Usermodel; namespace InABox.Wpf.Console; public static class ItemsControlExtensions { public static void ScrollIntoView( this ItemsControl control, object item) { var framework = control.ItemContainerGenerator.ContainerFromItem(item) as FrameworkElement; if (framework == null) return; framework.BringIntoView(); } public static void ScrollIntoView(this ItemsControl control) { var count = control.Items.Count; if (count == 0) return; var item = control.Items[count - 1]; control.ScrollIntoView(item); } } /// /// Interaction logic for Console.xaml /// public partial class ConsoleControl : UserControl, INotifyPropertyChanged { public static readonly DependencyProperty EnabledProperty = DependencyProperty.Register(nameof(Enabled), typeof(bool), typeof(ConsoleControl)); private CollectionViewSource _filtered; public CollectionViewSource Filtered { get => _filtered; set { _filtered = value; OnPropertyChanged(); } } public readonly ObservableCollection LogEntries; private readonly TimeSpan regexTimeOut = TimeSpan.FromMilliseconds(100); private Regex? searchRegex; public event PropertyChangedEventHandler? PropertyChanged; public event Action? OnLoadLog; public event Action? OnCloseLog; public bool _allowLoadLogButton = true; public bool AllowLoadLogButton { get => _allowLoadLogButton; set { _allowLoadLogButton = value; OnPropertyChanged(); OnPropertyChanged(nameof(ShowLoadLogButton)); OnPropertyChanged(nameof(ShowCloseLogButton)); } } private bool _loadedLog = false; public bool LoadedLog { get => _loadedLog; set { _loadedLog = value; OnPropertyChanged(); OnPropertyChanged(nameof(ShowLoadLogButton)); OnPropertyChanged(nameof(ShowCloseLogButton)); } } public bool ShowLoadLogButton => !LoadedLog && AllowLoadLogButton; public bool ShowCloseLogButton => LoadedLog && AllowLoadLogButton; public bool Enabled { get => (bool)GetValue(EnabledProperty); set => SetValue(EnabledProperty, value); } public ConsoleControl() { InitializeComponent(); Filtered = new CollectionViewSource(); LogEntries = new ObservableCollection(); Filtered.Source = LogEntries; Filtered.Filter += (sender, args) => { var logEntry = (LogEntry)args.Item; if (ShowImportant.IsChecked == true && !IsImportant(logEntry)) { args.Accepted = false; return; } if (UseRegEx.IsChecked == true && searchRegex != null) args.Accepted = string.IsNullOrWhiteSpace(Search.Text) || searchRegex.IsMatch(logEntry.DateTime) || searchRegex.IsMatch(logEntry.Type) || searchRegex.IsMatch(logEntry.User) || searchRegex.IsMatch(logEntry.Message); else args.Accepted = string.IsNullOrWhiteSpace(Search.Text) || logEntry.DateTime.Contains(Search.Text) || logEntry.Type.Contains(Search.Text) || logEntry.User.Contains(Search.Text) || logEntry.Message.Contains(Search.Text); }; } protected void OnPropertyChanged([CallerMemberName] string propertyName = "") { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } private static bool IsImportant(LogEntry entry) { return entry.Type == "IMPTNT"; } // DATE TRANSACTION TYPE USERID MESSAGE [GeneratedRegex("(\\d\\d:\\d\\d:\\d\\d\\.\\d\\d\\d)\\s*([0-9a-zA-Z]{8}(?:\\-[0-9a-zA-Z]{4}){3}\\-[0-9a-zA-Z]{12})?\\s*(\\S+) (.+)", RegexOptions.Singleline)] internal static partial Regex LogEntryParseRegex(); public static LogEntry? ParseLogMessage(string logLine) { var match = LogEntryParseRegex().Match(logLine); if (match.Success) { var date = match.Groups[1].Value; if (!Guid.TryParse(match.Groups[2].Value, out var transaction)) { transaction = Guid.Empty; } var type = match.Groups[3].Value; var remaining = match.Groups[4].Value; var user = remaining.Length >= 12 ? remaining[..12] : ""; var msg = remaining.Length >= 12 ? remaining[13..] : ""; return new LogEntry { DateTime = date, Type = type, User = user, Message = msg, Transaction = transaction }; } else { return null; } } public void LoadLogEntries(IEnumerable lines) { var logEntries = new List(); var numberSkipped = 0; foreach (var line in lines) { var logEntry = ParseLogMessage(line ?? ""); if(logEntry is not null) { var logType = logEntry.Type; if (logType == "ERROR" || logType == "INFO" || logType == "IMPTNT") logEntries.Add(logEntry); else if (string.IsNullOrWhiteSpace(logType)) numberSkipped++; } } if (numberSkipped > 0) { if (logEntries.Count == 0) SetErrorMessage("File does not contain valid log information!"); else SetErrorMessage(string.Format("Skipped {0} lines that did not contain valid log information", numberSkipped)); } Filtered.Source = logEntries; } public void LoadLogEntry(string line) { var logEntry = ParseLogMessage(line); if(logEntry is not null) { var logType = logEntry.Type; if (logType == "INFO" || logType == "ERROR" || logType == "IMPTNT") { LogEntries.Insert(0, logEntry); } } } private void Search_KeyDown(object sender, KeyEventArgs e) { if (e.Key == Key.Enter && UseRegEx.IsChecked == true) { try { searchRegex = new Regex(Search.Text, RegexOptions.Compiled, regexTimeOut); } catch (ArgumentException) { searchRegex = null; } Filtered.View.Refresh(); SetSearchStyleNormal(); } } private void SetSearchStyleChanged() { Search.Background = Brushes.White; } private void SetSearchStyleNormal() { Search.Background = Brushes.LightYellow; } private void Search_TextChanged(object sender, TextChangedEventArgs e) { if (UseRegEx.IsChecked != true) { Filtered.View.Refresh(); } else { if (string.IsNullOrWhiteSpace(Search.Text)) { searchRegex = null; Filtered.View.Refresh(); SetSearchStyleNormal(); } else { SetSearchStyleChanged(); } } } private void UseRegEx_Checked(object sender, RoutedEventArgs e) { try { searchRegex = new Regex(Search.Text, RegexOptions.Compiled, regexTimeOut); } catch (ArgumentException ex) { searchRegex = null; } Filtered.View.Refresh(); } private void UseRegEx_Unchecked(object sender, RoutedEventArgs e) { searchRegex = null; Filtered.View.Refresh(); SetSearchStyleNormal(); } public void SetErrorMessage(string? error) { if (string.IsNullOrWhiteSpace(error)) { Error.Content = ""; Error.Visibility = Visibility.Collapsed; } else { Error.Content = error; Error.Visibility = Visibility.Visible; } } private void CloseLog_Click(object sender, RoutedEventArgs e) { Filtered.Source = LogEntries; OnCloseLog?.Invoke(); } private void ShowImportant_Checked(object sender, RoutedEventArgs e) { Filtered.View.Refresh(); } private void ShowImportant_Unchecked(object sender, RoutedEventArgs e) { Filtered.View.Refresh(); } private void LoadLog_Click(object sender, RoutedEventArgs e) { OnLoadLog?.Invoke(); } } public abstract class Console : Window { public ConsoleControl ConsoleControl { get; set; } private readonly string Description; public Console(string description) { ConsoleControl = new ConsoleControl(); ConsoleControl.OnLoadLog += ConsoleControl_OnLoadLog; ConsoleControl.OnCloseLog += ConsoleControl_OnCloseLog; Content = ConsoleControl; Height = 800; Width = 1200; Loaded += Console_Loaded; Closing += Console_Closing; Title = description; Description = description; } private void ConsoleControl_OnCloseLog() { Title = Description; ConsoleControl.LoadedLog = false; } private void ConsoleControl_OnLoadLog() { var dialog = new OpenFileDialog { InitialDirectory = GetLogDirectory() }; if (dialog.ShowDialog() == true) { var lines = File.ReadLines(dialog.FileName); ConsoleControl.LoadLogEntries(lines); ConsoleControl.LoadedLog = true; Title = dialog.FileName; } } protected virtual void OnLoaded() { } protected virtual void OnClosing() { } private void Console_Closing(object? sender, CancelEventArgs e) { OnClosing(); } private void Console_Loaded(object sender, RoutedEventArgs e) { OnLoaded(); } protected abstract string GetLogDirectory(); }