|
@@ -0,0 +1,374 @@
|
|
|
+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;
|
|
|
+
|
|
|
+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);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/// <summary>
|
|
|
+/// Interaction logic for Console.xaml
|
|
|
+/// </summary>
|
|
|
+public partial class ConsoleControl : UserControl, INotifyPropertyChanged
|
|
|
+{
|
|
|
+ private CollectionViewSource _filtered;
|
|
|
+ public CollectionViewSource Filtered
|
|
|
+ {
|
|
|
+ get => _filtered;
|
|
|
+ set
|
|
|
+ {
|
|
|
+ _filtered = value;
|
|
|
+ OnPropertyChanged();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public readonly ObservableCollection<LogEntry> LogEntries;
|
|
|
+
|
|
|
+ private readonly TimeSpan regexTimeOut = TimeSpan.FromMilliseconds(100);
|
|
|
+
|
|
|
+ private Regex? searchRegex;
|
|
|
+
|
|
|
+ private bool _enabled = true;
|
|
|
+
|
|
|
+ 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 => _enabled;
|
|
|
+ set
|
|
|
+ {
|
|
|
+ _enabled = value;
|
|
|
+ OnPropertyChanged();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public ConsoleControl()
|
|
|
+ {
|
|
|
+ InitializeComponent();
|
|
|
+
|
|
|
+ Filtered = new CollectionViewSource();
|
|
|
+ LogEntries = new ObservableCollection<LogEntry>();
|
|
|
+ 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";
|
|
|
+ }
|
|
|
+
|
|
|
+ public static LogEntry ParseLogMessage(string logLine)
|
|
|
+ {
|
|
|
+ var datetime = logLine.Length > 32 ? logLine[..12] : string.Format("{0:HH:mm:ss.fff}", DateTime.Now);
|
|
|
+ var type = logLine.Length > 32 ? logLine.Substring(13, 6).Trim() : "INFO";
|
|
|
+ var user = logLine.Length > 32 ? logLine.Substring(20, 12) : "";
|
|
|
+ var msg = logLine.Length > 32 ? logLine[33..] : logLine;
|
|
|
+
|
|
|
+ return new LogEntry
|
|
|
+ {
|
|
|
+ DateTime = datetime,
|
|
|
+ Type = type,
|
|
|
+ User = user,
|
|
|
+ Message = msg
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ public void LoadLogEntries(IEnumerable<string> lines)
|
|
|
+ {
|
|
|
+ var logEntries = new List<LogEntry>();
|
|
|
+
|
|
|
+ var numberSkipped = 0;
|
|
|
+
|
|
|
+ foreach (var line in lines)
|
|
|
+ {
|
|
|
+ var logEntry = ParseLogMessage(line ?? "");
|
|
|
+ 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);
|
|
|
+ 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();
|
|
|
+}
|