using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Windows.Forms; using PolyTraderSharp.Services; namespace PolyTraderSharp.Ui.Views { /// /// Terminal-/Log-Ansicht. Designbar (siehe TerminalView.Designer.cs). Die Laufzeit- /// Abhängigkeit (TerminalLogger) wird per injiziert, damit der /// parameterlose Konstruktor für den VS-Designer nutzbar bleibt. /// public partial class TerminalView : Form { private TerminalLogger? _logger; private readonly ConcurrentQueue _logQueue = new(); private readonly System.Windows.Forms.Timer _uiLogTimer = new() { Interval = 250 }; private bool _autoScroll = true; private EventHandler? _logHandler; public TerminalView() { InitializeComponent(); // Steuerelemente + statische Eigenschaften stammen aus dem Designer. // Hier nur noch Verhalten verdrahten und einen sinnvollen Default wählen. if (cbLogLevel.Items.Count > 0) cbLogLevel.SelectedIndex = 0; btnAutoscroll.Click += (_, _) => ToggleAutoscroll(); _uiLogTimer.Tick += ProcessLogQueue; // Kontextmenü: Text aus dem Terminal kopieren (Ctrl+C funktioniert zusätzlich nativ). miCopy.Click += (_, _) => { if (rtbTerminal.SelectionLength > 0) rtbTerminal.Copy(); }; miSelectAll.Click += (_, _) => rtbTerminal.SelectAll(); miCopyAll.Click += (_, _) => CopyAllToClipboard(); miClear.Click += (_, _) => rtbTerminal.Clear(); // Log Viewer (S-0): JSONL-Tagesdateien menschenlesbar, filterbar nach Level/CID/Text. if (cbViewerLevel.Items.Count > 0) cbViewerLevel.SelectedIndex = 0; btnViewerLoad.Click += (_, _) => LoadViewer(); tbViewerCid.KeyDown += (_, e) => { if (e.KeyCode == Keys.Enter) { LoadViewer(); e.SuppressKeyPress = true; } }; tbViewerText.KeyDown += (_, e) => { if (e.KeyCode == Keys.Enter) { LoadViewer(); e.SuppressKeyPress = true; } }; // Doppelklick auf eine CID: komplette Signal-Kette anzeigen. dgvLogs.CellDoubleClick += (_, e) => { if (e.RowIndex < 0) return; if (dgvLogs.Rows[e.RowIndex].DataBoundItem is LogViewerRow row && !string.IsNullOrEmpty(row.Cid)) { tbViewerCid.Text = row.Cid; tbViewerText.Text = ""; LoadViewer(); } }; } // ===== Log Viewer ===== /// Anzeige-Zeile des Log Viewers (Bindung über DataPropertyName). private sealed class LogViewerRow { public string Time { get; init; } = ""; public string Level { get; init; } = ""; public string Cid { get; init; } = ""; public string Message { get; init; } = ""; } private void LoadViewer() { try { string path = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs", $"{dtViewerDate.Value:yyyy-MM-dd}.jsonl"); if (!System.IO.File.Exists(path)) { dgvLogs.DataSource = new System.ComponentModel.BindingList(); lblViewerStatus.Text = "Keine JSONL-Datei für dieses Datum."; return; } string levelFilter = cbViewerLevel.SelectedItem?.ToString() ?? "Alle"; string cidFilter = tbViewerCid.Text.Trim(); string textFilter = tbViewerText.Text.Trim(); var rows = new List(); foreach (string line in System.IO.File.ReadLines(path)) { var p = LogJson.ParseLine(line); if (p == null) continue; if (levelFilter != "Alle" && p.Level != levelFilter) continue; if (cidFilter.Length > 0 && !p.Cid.Contains(cidFilter, StringComparison.OrdinalIgnoreCase)) continue; if (textFilter.Length > 0 && !p.Message.Contains(textFilter, StringComparison.OrdinalIgnoreCase)) continue; rows.Add(new LogViewerRow { Time = p.Time, Level = p.Level, Cid = p.Cid, Message = p.Message }); if (rows.Count >= 20000) break; // UI-Schutz bei sehr großen Tagen } dgvLogs.DataSource = new System.ComponentModel.BindingList(rows); lblViewerStatus.Text = $"{rows.Count} Einträge."; } catch (Exception ex) { lblViewerStatus.Text = $"Fehler: {ex.Message}"; } } private void CopyAllToClipboard() { try { if (!string.IsNullOrEmpty(rtbTerminal.Text)) Clipboard.SetText(rtbTerminal.Text); } catch { /* Zwischenablage kann kurzzeitig belegt sein – bewusst ignorieren */ } } /// Verbindet die View mit dem Logger (Laufzeit-DI). public void Initialize(TerminalLogger logger) { _logger = logger; // Jüngste Historie vorladen, damit das Fenster beim Öffnen nicht leer ist. foreach (var e in _logger.GetHistory(TimeSpan.FromMinutes(10))) _logQueue.Enqueue(e); _logHandler = (_, e) => _logQueue.Enqueue(e); _logger.OnLogMessage += _logHandler; _uiLogTimer.Start(); Disposed += OnDisposed; } private void OnDisposed(object? sender, EventArgs e) { _uiLogTimer.Stop(); if (_logger != null && _logHandler != null) _logger.OnLogMessage -= _logHandler; } private void ToggleAutoscroll() { _autoScroll = !_autoScroll; btnAutoscroll.BackColor = _autoScroll ? System.Drawing.Color.LightGreen : System.Drawing.Color.IndianRed; btnAutoscroll.Text = _autoScroll ? "Stop Autoscroll" : "Start Autoscroll"; } private void ProcessLogQueue(object? sender, EventArgs e) { if (_logQueue.IsEmpty || !IsHandleCreated) return; string filter = cbLogLevel.SelectedItem?.ToString() ?? "Alle"; TimeZoneInfo berlinTz = TimeZoneInfo.FindSystemTimeZoneById("W. Europe Standard Time"); bool appended = false; int count = 0; const int maxProcess = 500; rtbTerminal.SuspendLayout(); while (count < maxProcess && _logQueue.TryDequeue(out var logEvent)) { count++; if (filter != "Alle" && logEvent.Level.ToString() != filter) continue; DateTime logTime = logEvent.Timestamp.Kind == DateTimeKind.Utc ? TimeZoneInfo.ConvertTimeFromUtc(logEvent.Timestamp, berlinTz) : TimeZoneInfo.ConvertTime(logEvent.Timestamp, berlinTz); string timeStr = $"[{logTime:HH:mm:ss}]"; System.Drawing.Color c = System.Drawing.Color.White; if (logEvent.Level == LogLevel.Error) c = System.Drawing.Color.Red; else if (logEvent.Level == LogLevel.Warning) c = System.Drawing.Color.Yellow; else if (logEvent.Level == LogLevel.Trade) c = System.Drawing.Color.LightGreen; else if (logEvent.Level == LogLevel.TradeReasoning) c = System.Drawing.Color.Orange; rtbTerminal.SelectionStart = rtbTerminal.TextLength; rtbTerminal.SelectionLength = 0; rtbTerminal.SelectionColor = c; rtbTerminal.AppendText($"{timeStr} [{logEvent.Level}] {logEvent.Message}\n"); appended = true; } if (appended) { if (rtbTerminal.TextLength > 80000) { rtbTerminal.Clear(); rtbTerminal.SelectionColor = System.Drawing.Color.LightPink; rtbTerminal.AppendText($"[{DateTime.Now:HH:mm:ss}] [System] Terminal Auto-Clear (RAM Limit erreicht). Vollständige Logs im Ordner /Logs.\n"); } if (_autoScroll) rtbTerminal.ScrollToCaret(); } rtbTerminal.ResumeLayout(); } } }