@page "/logfiles" @using System.IO @using MUnique.OpenMU.Web.AdminPanel.Properties @implements IDisposable @inject IJSRuntime JSRuntime OpenMU: @Resources.LogFiles
@foreach (var entry in this._files.OrderByDescending(f => f.LastWriteTime)) { var isSelected = this._selectedFile?.FullName == entry.FullName; }
@Resources.FileName @Resources.LastUpdate @Resources.Size Actions
@entry.LastWriteTime @FormatFileSize(entry.Length)
@if (this._selectedFile != null) {
Log Viewer: @this._selectedFile.Name @FormatFileSize(this._selectedFile.Length)
@if (!string.IsNullOrEmpty(this._searchText)) { }
@if (this._logLines.Count == 0) {
No log entries found.
} else { var filteredLines = GetFilteredLines(); @if (filteredLines.Count == 0) {
No log entries match your filter.
} else { @foreach (var line in filteredLines) {
@line
} } }
Showing @GetFilteredLines().Count of @this._logLines.Count lines (Last 300 lines loaded).
} @code { private readonly List _files = new (); private FileInfo? _selectedFile; private List _logLines = new (); private string _searchText = string.Empty; private bool _liveUpdate = false; private System.Threading.Timer? _timer; private bool _shouldScrollToBottom = false; /// protected override void OnInitialized() { this.RefreshFileList(); } private void RefreshFileList() { this._files.Clear(); var logsPath = Path.Combine(Directory.GetCurrentDirectory(), "logs"); if (Directory.Exists(logsPath)) { var files = Directory.GetFiles(logsPath); foreach (var filePath in files) { this._files.Add(new FileInfo(filePath)); } } } private void SelectFile(FileInfo file) { this._selectedFile = file; this._searchText = string.Empty; this.RefreshLogLines(); this._shouldScrollToBottom = true; this.SetupTimer(); } private void CloseViewer() { this._selectedFile = null; this._searchText = string.Empty; this._logLines.Clear(); this._liveUpdate = false; this.SetupTimer(); } private void ClearSearch() { this._searchText = string.Empty; } private void ToggleLiveUpdate(ChangeEventArgs e) { this._liveUpdate = (bool)(e.Value ?? false); this.SetupTimer(); } private void SetupTimer() { if (this._liveUpdate && this._selectedFile != null) { this._timer ??= new System.Threading.Timer(_ => { InvokeAsync(() => { this.RefreshLogLines(); this.StateHasChanged(); }); }, null, 0, 2000); } else { this._timer?.Dispose(); this._timer = null; } } private void RefreshLogLines() { if (this._selectedFile == null) { return; } this._selectedFile = new FileInfo(this._selectedFile.FullName); this._logLines = this.ReadLastLines(this._selectedFile.FullName, 300); } private List ReadLastLines(string path, int maxLines) { var lines = new List(); try { using var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); long offset = Math.Max(0, fs.Length - 102400); // Read last 100 KB fs.Seek(offset, SeekOrigin.Begin); using var reader = new StreamReader(fs, System.Text.Encoding.UTF8); if (offset > 0) { // Discard partial line reader.ReadLine(); } string? line; while ((line = reader.ReadLine()) != null) { lines.Add(line); } if (lines.Count > maxLines) { lines = lines.Skip(lines.Count - maxLines).ToList(); } } catch (Exception ex) { lines.Add($"Error reading log file: {ex.Message}"); } return lines; } private List GetFilteredLines() { if (string.IsNullOrWhiteSpace(this._searchText)) { return this._logLines; } return this._logLines .Where(line => line.Contains(this._searchText, StringComparison.OrdinalIgnoreCase)) .ToList(); } private string GetLineColorStyle(string line) { if (line.Contains("[Error]", StringComparison.OrdinalIgnoreCase) || line.Contains("[Critical]", StringComparison.OrdinalIgnoreCase)) { return "color: #ff6b6b; font-weight: bold;"; } if (line.Contains("[Warning]", StringComparison.OrdinalIgnoreCase)) { return "color: #feca57;"; } if (line.Contains("[Debug]", StringComparison.OrdinalIgnoreCase)) { return "color: #8a8d93; font-style: italic;"; } if (line.Contains("[Information]", StringComparison.OrdinalIgnoreCase)) { return "color: #1dd1a1;"; } return "color: #d1d2d6;"; } private async Task ScrollToBottom() { try { await JSRuntime.InvokeVoidAsync("eval", "var el = document.getElementById('log-terminal'); if (el) { el.scrollTop = el.scrollHeight; }"); } catch { // Ignore error } } /// protected override async Task OnAfterRenderAsync(bool firstRender) { if (this._shouldScrollToBottom) { this._shouldScrollToBottom = false; await this.ScrollToBottom(); } } /// public void Dispose() { this._timer?.Dispose(); } private string FormatFileSize(long size) { return size switch { (< 1024 << 10) => $"{Math.Round(size / 1024D, 2)} KiB", (< 1024 << 20) => $"{Math.Round(size * 1D / (1024 << 10), 2)} MiB", (< 1024L << 30) => $"{Math.Round(size * 1D / (1024L << 20), 2)} GiB", _ => $"{size} bytes" }; } }