diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..deea040 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,36 @@ +name: Build + +on: + push: + branches: ["**"] + pull_request: + branches: ["**"] + +jobs: + build: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v4 + + - name: Setup .NET 6 + uses: actions/setup-dotnet@v4 + with: + dotnet-version: "6.0.x" + + - name: Publish + run: > + dotnet publish csharp/ScalanceLogs.csproj + -c Release + -r win-x64 + --self-contained false + -p:PublishSingleFile=true + -p:IncludeNativeLibrariesForSelfExtract=true + -o publish + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: ScalanceLogs + path: publish/ + retention-days: 30 diff --git a/csharp/Helpers/IconHelper.cs b/csharp/Helpers/IconHelper.cs index 78064c5..fadd56b 100644 --- a/csharp/Helpers/IconHelper.cs +++ b/csharp/Helpers/IconHelper.cs @@ -73,7 +73,7 @@ private static Bitmap DrawBrand(int size) // ── Dots (3 circles, r=4, y=62, spaced 17px apart) ──────────────────── float dr = 4 * s, dy = 62 * s; - float[] dotX = [14 * s, 31 * s, 48 * s]; + float[] dotX = new float[] { 14 * s, 31 * s, 48 * s }; using var dotBrush = new SolidBrush(DotColor); foreach (var dx in dotX) g.FillEllipse(dotBrush, dx, dy, dr * 2, dr * 2); diff --git a/csharp/Helpers/PathValidator.cs b/csharp/Helpers/PathValidator.cs index bc779de..9eb265b 100644 --- a/csharp/Helpers/PathValidator.cs +++ b/csharp/Helpers/PathValidator.cs @@ -14,15 +14,16 @@ public static class PathValidator "ScalanceLogs", "logs"); private static readonly string[] ForbiddenRoots = - [ - Environment.GetFolderPath(Environment.SpecialFolder.Windows), - Environment.GetFolderPath(Environment.SpecialFolder.System), - Environment.GetFolderPath(Environment.SpecialFolder.SystemX86), - Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), - Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), - Environment.GetFolderPath(Environment.SpecialFolder.CommonProgramFiles), - Environment.GetFolderPath(Environment.SpecialFolder.CommonProgramFilesX86), - ]; + new string[] + { + Environment.GetFolderPath(Environment.SpecialFolder.Windows), + Environment.GetFolderPath(Environment.SpecialFolder.System), + Environment.GetFolderPath(Environment.SpecialFolder.SystemX86), + Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), + Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), + Environment.GetFolderPath(Environment.SpecialFolder.CommonProgramFiles), + Environment.GetFolderPath(Environment.SpecialFolder.CommonProgramFilesX86), + }; /// /// Returns a safe absolute path. If the supplied path is empty, unsafe, diff --git a/csharp/MainWindow.xaml.cs b/csharp/MainWindow.xaml.cs index 1e8dfea..f6f8c77 100644 --- a/csharp/MainWindow.xaml.cs +++ b/csharp/MainWindow.xaml.cs @@ -14,10 +14,10 @@ namespace ScalanceLogs; public partial class MainWindow : Window { - private readonly ObservableCollection _logEntries = []; - private readonly ObservableCollection _liveEntries = []; - private readonly ObservableCollection _activeSw = []; - private readonly ObservableCollection _fileGroups = []; + private readonly ObservableCollection _logEntries = new ObservableCollection(); + private readonly ObservableCollection _liveEntries = new ObservableCollection(); + private readonly ObservableCollection _activeSw = new ObservableCollection(); + private readonly ObservableCollection _fileGroups = new ObservableCollection(); private string? _currentFile; private string _activeQuickFilter = ""; @@ -106,7 +106,7 @@ private static List GroupFiles(string[] files) g.Switches.Add(new FileItem { Display = ip, FileName = f }); } } - return [.. map.Values]; + return map.Values.ToList(); } private void FileTree_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs e) diff --git a/csharp/Models/AppSettings.cs b/csharp/Models/AppSettings.cs index 4e23e9c..b1efd72 100644 --- a/csharp/Models/AppSettings.cs +++ b/csharp/Models/AppSettings.cs @@ -12,43 +12,46 @@ public class AppSettings public string Theme { get; set; } = "Cyber"; public bool BalloonNotifications { get; set; } = true; // Labels that trigger balloon. Empty = ALL messages. - public List BalloonLabels { get; set; } = ["EMERG", "ALERT", "CRIT", "ERROR", - "LINK DOWN", "LINK UP"]; + public List BalloonLabels { get; set; } = new List { "EMERG", "ALERT", "CRIT", "ERROR", + "LINK DOWN", "LINK UP" }; // Per-theme color overrides (key = theme name, value = {brushKey → hex}) public Dictionary> ThemeOverrides { get; set; } = new(); - public List SwitchNames { get; set; } = []; + public List SwitchNames { get; set; } = new List(); public List EventPatterns { get; set; } = DefaultEventPatterns(); public List MessageTypes { get; set; } = DefaultMessageTypes(); public List QuickFilters { get; set; } = DefaultQuickFilters(); public static List DefaultEventPatterns() => - [ - @"link\s+(up|down)", - @"port.*(up|down)", - @"(fault|error|fail)", - ]; + new List + { + @"link\s+(up|down)", + @"port.*(up|down)", + @"(fault|error|fail)", + }; public static List DefaultMessageTypes() => - [ - new() { Pattern = @"link\s+down", Label = "LINK DOWN", Color = "#ff6b6b", Bg = "rgba(255,107,107,0.15)" }, - new() { Pattern = @"link\s+up", Label = "LINK UP", Color = "#4a9e72", Bg = "rgba(74,158,114,0.12)" }, - new() { Pattern = @"time synchronized", Label = "NTP SYNC", Color = "#4fa8c5", Bg = "rgba(79,168,197,0.10)" }, - new() { Pattern = @"time not synchronized", Label = "NTP LOST", Color = "#b8913a", Bg = "rgba(184,145,58,0.12)" }, - new() { Pattern = @"configuration changed", Label = "CFG CHG", Color = "#b8913a", Bg = "rgba(184,145,58,0.10)" }, - new() { Pattern = @"logged in", Label = "LOGIN", Color = "#a8b4cc", Bg = "rgba(168,180,204,0.08)" }, - new() { Pattern = @"logged out|inactivity", Label = "LOGOUT", Color = "#556070", Bg = "rgba(85,96,112,0.10)" }, - new() { Pattern = @"log file cleared", Label = "LOG CLR", Color = "#556070", Bg = "rgba(85,96,112,0.08)" }, - ]; + new List + { + new MessageTypeEntry { Pattern = @"link\s+down", Label = "LINK DOWN", Color = "#ff6b6b", Bg = "rgba(255,107,107,0.15)" }, + new MessageTypeEntry { Pattern = @"link\s+up", Label = "LINK UP", Color = "#4a9e72", Bg = "rgba(74,158,114,0.12)" }, + new MessageTypeEntry { Pattern = @"time synchronized", Label = "NTP SYNC", Color = "#4fa8c5", Bg = "rgba(79,168,197,0.10)" }, + new MessageTypeEntry { Pattern = @"time not synchronized", Label = "NTP LOST", Color = "#b8913a", Bg = "rgba(184,145,58,0.12)" }, + new MessageTypeEntry { Pattern = @"configuration changed", Label = "CFG CHG", Color = "#b8913a", Bg = "rgba(184,145,58,0.10)" }, + new MessageTypeEntry { Pattern = @"logged in", Label = "LOGIN", Color = "#a8b4cc", Bg = "rgba(168,180,204,0.08)" }, + new MessageTypeEntry { Pattern = @"logged out|inactivity", Label = "LOGOUT", Color = "#556070", Bg = "rgba(85,96,112,0.10)" }, + new MessageTypeEntry { Pattern = @"log file cleared", Label = "LOG CLR", Color = "#556070", Bg = "rgba(85,96,112,0.08)" }, + }; public static List DefaultQuickFilters() => - [ - new() { Label = "Link", Query = "link" }, - new() { Label = "NTP", Query = "ntp" }, - new() { Label = "Admin", Query = "admin" }, - new() { Label = "Config", Query = "configuration" }, - ]; + new List + { + new QuickFilterEntry { Label = "Link", Query = "link" }, + new QuickFilterEntry { Label = "NTP", Query = "ntp" }, + new QuickFilterEntry { Label = "Admin", Query = "admin" }, + new QuickFilterEntry { Label = "Config", Query = "configuration" }, + }; } public class SwitchNameEntry diff --git a/csharp/Models/FileGroup.cs b/csharp/Models/FileGroup.cs index b9df40c..0c79356 100644 --- a/csharp/Models/FileGroup.cs +++ b/csharp/Models/FileGroup.cs @@ -6,7 +6,7 @@ public class FileGroup { public string Date { get; set; } = ""; public string? EventsFile { get; set; } - public ObservableCollection Switches { get; set; } = []; + public ObservableCollection Switches { get; set; } = new ObservableCollection(); // Display name for the group header node public string Header => EventsFile ?? Date; diff --git a/csharp/ScalanceLogs.csproj b/csharp/ScalanceLogs.csproj index 80641a6..ac52c16 100644 --- a/csharp/ScalanceLogs.csproj +++ b/csharp/ScalanceLogs.csproj @@ -1,7 +1,7 @@ WinExe - net9.0-windows + net6.0-windows enable enable true diff --git a/csharp/Services/LogManager.cs b/csharp/Services/LogManager.cs index 0086c43..157de11 100644 --- a/csharp/Services/LogManager.cs +++ b/csharp/Services/LogManager.cs @@ -13,7 +13,7 @@ public static class LogManager private static string _logDir = ""; // (key, writer, lastUsedTicks) — small enough that O(N) eviction is fine - private static readonly Dictionary Writers = []; + private static readonly Dictionary Writers = new Dictionary(); private static readonly object Lock = new(); private static string _currentDate = ""; private static Timer? _cleanupTimer; @@ -104,13 +104,13 @@ public static string[] GetLogFiles() .OrderBy(f => f) .ToArray()!; } - catch { return []; } + catch { return Array.Empty(); } } public static string[] ReadLines(string filename, int count, string search) { var safeName = Path.GetFileName(filename); - if (string.IsNullOrEmpty(safeName)) return []; + if (string.IsNullOrEmpty(safeName)) return Array.Empty(); var path = Path.Combine(_logDir, safeName); try { @@ -128,7 +128,7 @@ public static string[] ReadLines(string filename, int count, string search) all = all.Where(l => l.Contains(search, StringComparison.OrdinalIgnoreCase)); return all.TakeLast(count).ToArray(); } - catch { return []; } + catch { return Array.Empty(); } } private static string Today() => DateTime.Today.ToString("yyyy-MM-dd"); diff --git a/csharp/Services/SyslogParser.cs b/csharp/Services/SyslogParser.cs index 4288cfa..0814146 100644 --- a/csharp/Services/SyslogParser.cs +++ b/csharp/Services/SyslogParser.cs @@ -23,7 +23,7 @@ public static class SyslogParser private static readonly Regex SwitchTagRe = new(@"\(([^)]+)\)", RegexOptions.Compiled); private static readonly string[] SeverityLabels = - ["EMERG", "ALERT", "CRIT", "ERROR", "WARN", "NOTICE", "INFO", "DEBUG"]; + new string[] { "EMERG", "ALERT", "CRIT", "ERROR", "WARN", "NOTICE", "INFO", "DEBUG" }; // ── Parse raw syslog datagram ──────────────────────────────── public static (int severity, string hostname, string message) diff --git a/csharp/Services/ThemeManager.cs b/csharp/Services/ThemeManager.cs index 6635794..d80563c 100644 --- a/csharp/Services/ThemeManager.cs +++ b/csharp/Services/ThemeManager.cs @@ -6,7 +6,7 @@ namespace ScalanceLogs.Services; public static class ThemeManager { - public static readonly string[] Available = ["Cyber", "Light", "Midnight", "SlateGlass"]; + public static readonly string[] Available = new string[] { "Cyber", "Light", "Midnight", "SlateGlass" }; // ── Default palettes ──────────────────────────────────────────── public static readonly Dictionary> Defaults = new() diff --git a/csharp/SettingsWindow.xaml.cs b/csharp/SettingsWindow.xaml.cs index 39af278..2dddfa1 100644 --- a/csharp/SettingsWindow.xaml.cs +++ b/csharp/SettingsWindow.xaml.cs @@ -48,14 +48,14 @@ public class BalloonLabelItem public partial class SettingsWindow : Window { private static readonly string[] StandardLabels = - ["EMERG", "ALERT", "CRIT", "ERROR", "WARN", "NOTICE", "INFO", "DEBUG"]; + new string[] { "EMERG", "ALERT", "CRIT", "ERROR", "WARN", "NOTICE", "INFO", "DEBUG" }; - private readonly ObservableCollection _switches = []; - private readonly ObservableCollection _msgTypes = []; - private readonly ObservableCollection _quickFilters = []; - private readonly ObservableCollection _patterns = []; - private readonly ObservableCollection _balloonItems = []; - private readonly ObservableCollection _themeCards = []; + private readonly ObservableCollection _switches = new ObservableCollection(); + private readonly ObservableCollection _msgTypes = new ObservableCollection(); + private readonly ObservableCollection _quickFilters = new ObservableCollection(); + private readonly ObservableCollection _patterns = new ObservableCollection(); + private readonly ObservableCollection _balloonItems = new ObservableCollection(); + private readonly ObservableCollection _themeCards = new ObservableCollection(); public SettingsWindow() { @@ -197,13 +197,13 @@ private bool ApplySettings() s.AutoStart = AutoStartCheck.IsChecked == true; s.BalloonNotifications = BalloonCheck.IsChecked == true; s.BalloonLabels = BalloonAllCheck.IsChecked == true - ? [] - : [.. _balloonItems.Where(b => b.IsChecked).Select(b => b.Label)]; + ? new List() + : _balloonItems.Where(b => b.IsChecked).Select(b => b.Label).ToList(); - s.SwitchNames = [.. _switches]; - s.EventPatterns = [.. _patterns]; - s.MessageTypes = [.. _msgTypes]; - s.QuickFilters = [.. _quickFilters]; + s.SwitchNames = _switches.ToList(); + s.EventPatterns = _patterns.ToList(); + s.MessageTypes = _msgTypes.ToList(); + s.QuickFilters = _quickFilters.ToList(); SettingsService.Save(s); AutostartHelper.Set(s.AutoStart); diff --git a/csharp/ThemeEditorWindow.xaml.cs b/csharp/ThemeEditorWindow.xaml.cs index 88e518b..42f733e 100644 --- a/csharp/ThemeEditorWindow.xaml.cs +++ b/csharp/ThemeEditorWindow.xaml.cs @@ -46,18 +46,19 @@ private void OnPC([CallerMemberName] string? n = null) => public partial class ThemeEditorWindow : Window { private readonly string _themeName; - private readonly ObservableCollection _rows = []; + private readonly ObservableCollection _rows = new ObservableCollection(); // Groups and their keys (TransBrush is always transparent — skip) private static readonly (string Group, string[] Keys)[] Layout = - [ - ("BACKGROUNDS", ["BgBrush", "SurfaceBrush", "SubtleBrush"]), - ("ACCENT", ["AccentBrush", "Accent2Brush"]), - ("TEXT", ["TextBrush", "TextDimBrush", "MutedBrush"]), - ("STATUS", ["GreenBrush", "RedBrush", "YellowBrush"]), - ("BORDERS & HOVER", ["BorderBrush", "HoverBg", "SelectBg", "PressedBg"]), - ("LOG ROW BACKGROUNDS", ["RowErrorBg", "RowWarnBg"]), - ]; + new (string Group, string[] Keys)[] + { + ("BACKGROUNDS", new string[] { "BgBrush", "SurfaceBrush", "SubtleBrush" }), + ("ACCENT", new string[] { "AccentBrush", "Accent2Brush" }), + ("TEXT", new string[] { "TextBrush", "TextDimBrush", "MutedBrush" }), + ("STATUS", new string[] { "GreenBrush", "RedBrush", "YellowBrush" }), + ("BORDERS & HOVER", new string[] { "BorderBrush", "HoverBg", "SelectBg", "PressedBg" }), + ("LOG ROW BACKGROUNDS", new string[] { "RowErrorBg", "RowWarnBg" }), + }; public ThemeEditorWindow(string themeName) {