diff --git a/IntelliTrader.Trading.Tests/HomeControllerTests.cs b/IntelliTrader.Trading.Tests/HomeControllerTests.cs new file mode 100644 index 0000000..1c4d149 --- /dev/null +++ b/IntelliTrader.Trading.Tests/HomeControllerTests.cs @@ -0,0 +1,115 @@ +using System; +using System.IO; +using System.Text; +using IntelliTrader.Web.Controllers; +using Microsoft.AspNetCore.Mvc; +using Xunit; + +namespace IntelliTrader.Trading.Tests +{ + public class HomeControllerTests + { + [Fact] + public void DownloadLog_ReturnsNotFound_WhenNoMatchingFile() + { + // Arrange + var controller = new HomeController(); + + // Act + var result = controller.DownloadLog("nonexistent_log_type_12345"); + + // Assert + Assert.IsType(result); + var notFoundResult = (NotFoundObjectResult)result; + Assert.Equal("Log file not found.", notFoundResult.Value); + } + + [Fact] + public void DownloadLog_ReturnsFileStreamResult_WhenGeneralLogExists() + { + // Arrange + var controller = new HomeController(); + string logDirectory = Path.Combine(Directory.GetCurrentDirectory(), "log"); + if (!Directory.Exists(logDirectory)) + { + Directory.CreateDirectory(logDirectory); + } + + string testFileName = $"{DateTime.Now:yyyy-MM-dd-HHmmss}-test-general.txt"; + string testFilePath = Path.Combine(logDirectory, testFileName); + string testContent = "[INF] 2026-08-12 10:00:00 Test log entry for DownloadLog test."; + + try + { + File.WriteAllText(testFilePath, testContent, Encoding.UTF8); + + // Act + var result = controller.DownloadLog("general"); + + // Assert + Assert.IsType(result); + var fileResult = (FileStreamResult)result; + Assert.Equal("text/plain", fileResult.ContentType); + Assert.False(string.IsNullOrEmpty(fileResult.FileDownloadName)); + Assert.EndsWith("-general.txt", fileResult.FileDownloadName); + + using (var reader = new StreamReader(fileResult.FileStream, Encoding.UTF8)) + { + string content = reader.ReadToEnd(); + Assert.Contains("Test log entry for DownloadLog test.", content); + } + } + finally + { + if (File.Exists(testFilePath)) + { + File.Delete(testFilePath); + } + } + } + + [Fact] + public void DownloadLog_ReturnsFileStreamResult_WhenTradesLogExists() + { + // Arrange + var controller = new HomeController(); + string logDirectory = Path.Combine(Directory.GetCurrentDirectory(), "log"); + if (!Directory.Exists(logDirectory)) + { + Directory.CreateDirectory(logDirectory); + } + + string testFileName = $"{DateTime.Now:yyyy-MM-dd-HHmmss}-test-trades.txt"; + string testFilePath = Path.Combine(logDirectory, testFileName); + string testContent = "TradeResult {\"Pair\":\"BTCUSDT\",\"Profit\":10.5}"; + + try + { + File.WriteAllText(testFilePath, testContent, Encoding.UTF8); + + // Act + var result = controller.DownloadLog("trades"); + + // Assert + Assert.IsType(result); + var fileResult = (FileStreamResult)result; + Assert.Equal("text/plain", fileResult.ContentType); + Assert.False(string.IsNullOrEmpty(fileResult.FileDownloadName)); + Assert.EndsWith("-trades.txt", fileResult.FileDownloadName); + + using (var reader = new StreamReader(fileResult.FileStream, Encoding.UTF8)) + { + string content = reader.ReadToEnd(); + Assert.Contains("TradeResult", content); + } + } + finally + { + if (File.Exists(testFilePath)) + { + File.Delete(testFilePath); + } + } + } + } +} diff --git a/IntelliTrader.Trading.Tests/IntelliTrader.Trading.Tests.csproj b/IntelliTrader.Trading.Tests/IntelliTrader.Trading.Tests.csproj index 3d88629..6d1e7bb 100644 --- a/IntelliTrader.Trading.Tests/IntelliTrader.Trading.Tests.csproj +++ b/IntelliTrader.Trading.Tests/IntelliTrader.Trading.Tests.csproj @@ -22,6 +22,7 @@ + diff --git a/IntelliTrader.Web/Controllers/HomeController.cs b/IntelliTrader.Web/Controllers/HomeController.cs index 27edbbc..c76b7a3 100644 --- a/IntelliTrader.Web/Controllers/HomeController.cs +++ b/IntelliTrader.Web/Controllers/HomeController.cs @@ -538,6 +538,29 @@ public IActionResult Help(string lang = "en") return View(model); } + [HttpGet] + public IActionResult DownloadLog(string type = "general") + { + try + { + string pattern = "general".Equals(type, StringComparison.OrdinalIgnoreCase) ? "*-general.txt" : "*-trades.txt"; + string filePath = GetLatestLogFilePath(pattern); + + if (string.IsNullOrEmpty(filePath) || !System.IO.File.Exists(filePath)) + { + return NotFound("Log file not found."); + } + + var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + string fileName = Path.GetFileName(filePath); + return File(fileStream, "text/plain", fileName); + } + catch (Exception ex) + { + return BadRequest($"Unable to download log file: {ex.Message}"); + } + } + [HttpGet] public IActionResult PollLogs(string type = "general", int maxLines = 100) { diff --git a/IntelliTrader.Web/Static/Scripts/Views/dashboard.js b/IntelliTrader.Web/Static/Scripts/Views/dashboard.js index 1a2d24f..b8fa523 100644 --- a/IntelliTrader.Web/Static/Scripts/Views/dashboard.js +++ b/IntelliTrader.Web/Static/Scripts/Views/dashboard.js @@ -205,6 +205,8 @@ function setLogType(type) { $("#logTypeGeneralBtn").removeClass("active"); } + $("#downloadLogMonitorBtn").attr("href", "/Home/DownloadLog?type=" + type); + $("#logTerminal").html('
Loading logs...
'); pollLiveLogs(); } diff --git a/IntelliTrader.Web/Views/Home/Dashboard.cshtml b/IntelliTrader.Web/Views/Home/Dashboard.cshtml index 29406e5..7c4601d 100644 --- a/IntelliTrader.Web/Views/Home/Dashboard.cshtml +++ b/IntelliTrader.Web/Views/Home/Dashboard.cshtml @@ -457,9 +457,14 @@
Live Log Monitor
-
- - +
+
+ + +
+ + Скачать лог +
@@ -566,7 +571,10 @@ - + + + Скачать лог +
@@ -634,7 +642,12 @@ const intervalId = setInterval(fetchLogs, 3000); logTypeSelect.addEventListener("change", function() { + const logType = logTypeSelect.value; lastLogContent = ""; + const downloadBtn = document.getElementById("downloadTradeLogBtn"); + if (downloadBtn) { + downloadBtn.href = "/Home/DownloadLog?type=" + logType; + } logTerminal.innerHTML = '
Loading logs...
'; fetchLogs(); }); diff --git a/magda_agent_system/agent_tasks.json b/magda_agent_system/agent_tasks.json index 42a30c7..2eead1f 100644 --- a/magda_agent_system/agent_tasks.json +++ b/magda_agent_system/agent_tasks.json @@ -476,7 +476,7 @@ }, { "id": "web-dashboard-log-download", - "status": "todo", + "status": "done", "area": "web", "risk": "low", "title": "Add log file download button on Web Dashboard", @@ -505,6 +505,22 @@ "acceptance": [ "Users can click export on the Stats page to download summary statistics." ] + }, + { + "id": "web-dashboard-table-filtering", + "status": "todo", + "area": "web", + "risk": "low", + "title": "Enhance trading pair table search and filtering options", + "description": "Add quick status filters (e.g. Trailing Buys, Trailing Sells, Active DCA) to the Dashboard trading pair DataTables.", + "allowed_paths": [ + "IntelliTrader.Web/Views/**", + "IntelliTrader.Web/Static/**", + "magda_agent_system/agent_tasks.json" + ], + "acceptance": [ + "Dashboard UI provides quick filter buttons to filter active trading pairs by status." + ] } ] }