Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 115 additions & 0 deletions IntelliTrader.Trading.Tests/HomeControllerTests.cs
Original file line number Diff line number Diff line change
@@ -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<NotFoundObjectResult>(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<FileStreamResult>(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<FileStreamResult>(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);
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
<ItemGroup>
<ProjectReference Include="..\IntelliTrader.Core\IntelliTrader.Core.csproj" />
<ProjectReference Include="..\IntelliTrader.Trading\IntelliTrader.Trading.csproj" />
<ProjectReference Include="..\IntelliTrader.Web\IntelliTrader.Web.csproj" />
</ItemGroup>

</Project>
23 changes: 23 additions & 0 deletions IntelliTrader.Web/Controllers/HomeController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
2 changes: 2 additions & 0 deletions IntelliTrader.Web/Static/Scripts/Views/dashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,8 @@ function setLogType(type) {
$("#logTypeGeneralBtn").removeClass("active");
}

$("#downloadLogMonitorBtn").attr("href", "/Home/DownloadLog?type=" + type);

$("#logTerminal").html('<div class="text-muted">Loading logs...</div>');
pollLiveLogs();
}
Expand Down
21 changes: 17 additions & 4 deletions IntelliTrader.Web/Views/Home/Dashboard.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -457,9 +457,14 @@
<div class="card mt-4" style="background-color: var(--card-bg); color: var(--text-color); border-radius: 4px; border: 1px solid var(--card-border); margin-top: 20px;">
<div class="card-header" style="background-color: var(--card-header-bg); border-bottom: 1px solid var(--card-border); padding: 10px 15px; display: flex; align-items: center; justify-content: space-between;">
<h5 class="mb-0" style="color: var(--text-light); font-size: 16px; margin: 0;"><i class="fas fa-terminal mr-2"></i>Live Log Monitor</h5>
<div class="btn-group btn-group-sm" role="group">
<button type="button" class="btn btn-sm btn-outline-light active" id="logTypeGeneralBtn" style="border-color: var(--card-border); margin-right: 5px;" onclick="setLogType('general')">General Log</button>
<button type="button" class="btn btn-sm btn-outline-light" id="logTypeTradesBtn" style="border-color: var(--card-border);" onclick="setLogType('trades')">Trades Log</button>
<div class="d-flex align-items-center">
<div class="btn-group btn-group-sm mr-2" role="group">
<button type="button" class="btn btn-sm btn-outline-light active" id="logTypeGeneralBtn" style="border-color: var(--card-border); margin-right: 5px;" onclick="setLogType('general')">General Log</button>
<button type="button" class="btn btn-sm btn-outline-light" id="logTypeTradesBtn" style="border-color: var(--card-border);" onclick="setLogType('trades')">Trades Log</button>
</div>
<a id="downloadLogMonitorBtn" href="/Home/DownloadLog?type=general" class="btn btn-sm btn-outline-primary" style="margin-left: 5px;">
<i class="fas fa-download mr-1"></i> Скачать лог
</a>
</div>
</div>
<div class="card-body p-0">
Expand Down Expand Up @@ -566,7 +571,10 @@
<option value="trades" selected>Trade Logs</option>
<option value="general">General Logs</option>
</select>
<button id="clearTerminalBtn" class="btn btn-sm btn-outline-secondary" style="border-color: var(--card-border); color: var(--text-color);">Clear</button>
<button id="clearTerminalBtn" class="btn btn-sm btn-outline-secondary" style="border-color: var(--card-border); color: var(--text-color); margin-right: 10px;">Clear</button>
<a id="downloadTradeLogBtn" href="/Home/DownloadLog?type=trades" class="btn btn-sm btn-outline-primary">
<i class="fas fa-download mr-1"></i> Скачать лог
</a>
</div>
</div>
<div class="card-body" id="tradeLogTerminal" style="height: 250px; overflow-y: auto; font-size: 13px; line-height: 1.5; padding: 10px; background-color: var(--terminal-bg); border-radius: 0 0 4px 4px;">
Expand Down Expand Up @@ -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 = '<div class="text-muted">Loading logs...</div>';
fetchLogs();
});
Expand Down
18 changes: 17 additions & 1 deletion magda_agent_system/agent_tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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."
]
}
]
}