feat: Bot management improvements with wallet configuration and duplicate detection

This commit is contained in:
2025-10-10 12:34:00 +01:00
parent 91000035f5
commit 7008a95df3
9 changed files with 408 additions and 13 deletions

View File

@@ -307,6 +307,47 @@ public class BotsController : Controller
return RedirectToAction(nameof(Details), new { id });
}
// POST: Admin/Bots/UpdateWallets/5
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> UpdateWallets(Guid id, Dictionary<string, string> wallets)
{
try
{
var bot = await _botService.GetBotByIdAsync(id);
if (bot == null)
{
TempData["Error"] = "Bot not found";
return RedirectToAction(nameof(Index));
}
// Get current settings
var settings = bot.Settings ?? new Dictionary<string, object>();
// Filter out empty wallet addresses
var validWallets = wallets
.Where(w => !string.IsNullOrWhiteSpace(w.Value))
.ToDictionary(w => w.Key, w => w.Value.Trim());
// Update or add wallets section
settings["wallets"] = validWallets;
// Save settings
await _botService.UpdateBotSettingsAsync(id, new UpdateBotSettingsDto { Settings = settings });
_logger.LogInformation("Updated {Count} wallet addresses for bot {BotId}", validWallets.Count, id);
TempData["Success"] = $"Updated {validWallets.Count} wallet address(es) successfully";
return RedirectToAction(nameof(Edit), new { id });
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to update wallets for bot {BotId}", id);
TempData["Error"] = "Failed to update wallet addresses";
return RedirectToAction(nameof(Edit), new { id });
}
}
// GET: Admin/Bots/RegenerateKey/5
public IActionResult RegenerateKey(Guid id)
{

View File

@@ -49,6 +49,63 @@
</div>
</div>
<div class="card mb-3">
<div class="card-header">
<h5 class="mb-0">💰 Payment Wallets</h5>
</div>
<div class="card-body">
<p class="text-muted">Configure cryptocurrency wallet addresses for direct payments. These are used as fallback when payment gateway is unavailable.</p>
@{
var wallets = new Dictionary<string, string>();
try
{
var settings = System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, System.Text.Json.JsonElement>>(@settingsJson);
if (settings != null && settings.ContainsKey("wallets"))
{
wallets = System.Text.Json.JsonSerializer.Deserialize<Dictionary<string, string>>(settings["wallets"].GetRawText()) ?? new Dictionary<string, string>();
}
}
catch { }
var supportedCurrencies = new[] {
new { Code = "BTC", Name = "Bitcoin", Placeholder = "bc1q... or 1... or 3..." },
new { Code = "XMR", Name = "Monero", Placeholder = "4... (primary address)" },
new { Code = "LTC", Name = "Litecoin", Placeholder = "ltc1q... or L... or M..." },
new { Code = "DOGE", Name = "Dogecoin", Placeholder = "D..." },
new { Code = "ETH", Name = "Ethereum", Placeholder = "0x..." },
new { Code = "ZEC", Name = "Zcash", Placeholder = "t1... or zs1..." },
new { Code = "DASH", Name = "Dash", Placeholder = "X..." }
};
}
<form asp-action="UpdateWallets" asp-route-id="@Model.Id" method="post" id="walletsForm">
@Html.AntiForgeryToken()
@foreach (var currency in supportedCurrencies)
{
<div class="mb-3">
<label for="wallet_@currency.Code" class="form-label">
<strong>@currency.Code</strong> - @currency.Name
</label>
<input type="text"
name="wallets[@currency.Code]"
id="wallet_@currency.Code"
class="form-control font-monospace"
placeholder="@currency.Placeholder"
value="@(wallets.ContainsKey(currency.Code) ? wallets[currency.Code] : "")" />
</div>
}
<div class="alert alert-info">
<strong>Note:</strong> Only configured wallets will be available as payment options. Leave blank to disable direct payments for that cryptocurrency.
</div>
<button type="submit" class="btn btn-primary w-100">
<i class="bi bi-wallet2"></i> Save Wallet Addresses
</button>
</form>
</div>
</div>
<div class="card">
<div class="card-header">
<h5 class="mb-0">Bot Configuration (JSON)</h5>

View File

@@ -15,6 +15,86 @@
</a>
</p>
@{
// Detect duplicates by platform username
var duplicateGroups = Model
.Where(b => !string.IsNullOrEmpty(b.PlatformUsername))
.GroupBy(b => b.PlatformUsername)
.Where(g => g.Count() > 1)
.ToList();
if (duplicateGroups.Any())
{
<div class="alert alert-warning">
<h5 class="alert-heading"><i class="bi bi-exclamation-triangle"></i> Duplicate Bots Detected</h5>
<p>Found <strong>@duplicateGroups.Count duplicate bot group(s)</strong> with multiple entries for the same Telegram username.</p>
<p>This usually happens when the bot container restarts without a saved API key. The system now prevents this automatically.</p>
<button type="button" class="btn btn-sm btn-outline-primary" data-bs-toggle="collapse" data-bs-target="#duplicateDetails">
Show Details
</button>
<div id="duplicateDetails" class="collapse mt-3">
@foreach (var group in duplicateGroups)
{
<div class="card mb-2">
<div class="card-body">
<h6 class="card-title">@@@group.Key (@group.Count() entries)</h6>
<table class="table table-sm">
<thead>
<tr>
<th>Created</th>
<th>Last Seen</th>
<th>Status</th>
<th>Sessions</th>
<th>Action</th>
</tr>
</thead>
<tbody>
@foreach (var bot in group.OrderByDescending(b => b.LastSeenAt ?? b.CreatedAt))
{
var isNewest = bot == group.OrderByDescending(b => b.LastSeenAt ?? b.CreatedAt).First();
<tr class="@(isNewest ? "table-success" : "")">
<td>@bot.CreatedAt.ToString("yyyy-MM-dd HH:mm")</td>
<td>
@if (bot.LastSeenAt.HasValue)
{
@bot.LastSeenAt.Value.ToString("yyyy-MM-dd HH:mm")
}
else
{
<span class="text-muted">Never</span>
}
</td>
<td><span class="badge bg-@(bot.Status == LittleShop.Enums.BotStatus.Active ? "success" : "secondary")">@bot.Status</span></td>
<td>@bot.TotalSessions</td>
<td>
@if (isNewest)
{
<span class="badge bg-success">Keep (Most Recent)</span>
}
else
{
<form action="/Admin/Bots/Delete/@bot.Id" method="post" style="display:inline;">
@Html.AntiForgeryToken()
<button type="submit" class="btn btn-sm btn-outline-danger"
onclick="return confirm('Delete this duplicate bot entry? Sessions: @bot.TotalSessions')">
<i class="bi bi-trash"></i> Delete
</button>
</form>
}
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
}
</div>
</div>
}
}
@if (TempData["Success"] != null)
{
<div class="alert alert-success alert-dismissible fade show" role="alert">