- Add discovery API endpoints to TeleBot (probe, initialize, configure, status) - Add LivenessService for LittleShop connectivity monitoring with 5min shutdown - Add BotDiscoveryService to LittleShop for remote bot management - Add Admin UI: DiscoverRemote wizard, RepushConfig page, status badges - Add remote discovery fields to Bot model (RemoteAddress, RemotePort, etc.) - Add CheckRemoteStatus and RepushConfig controller actions - Update Index/Details views to show remote bot indicators - Shared secret authentication for discovery, BotKey for post-init 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
270 lines
13 KiB
Plaintext
270 lines
13 KiB
Plaintext
@model IEnumerable<LittleShop.DTOs.BotDto>
|
|
|
|
@{
|
|
ViewData["Title"] = "Bot Management";
|
|
}
|
|
|
|
<h1>Bot Management</h1>
|
|
|
|
<p>
|
|
<a href="/Admin/Bots/DiscoverRemote" class="btn btn-success">
|
|
<i class="fas fa-satellite-dish"></i> Discover Remote Bot
|
|
</a>
|
|
<a href="/Admin/Bots/Wizard" class="btn btn-primary">
|
|
<i class="fas fa-magic"></i> Create Telegram Bot (Wizard)
|
|
</a>
|
|
<a href="/Admin/Bots/Create" class="btn btn-outline-secondary">
|
|
<i class="fas fa-plus"></i> Manual Registration
|
|
</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">
|
|
@TempData["Success"]
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
|
</div>
|
|
}
|
|
|
|
@if (TempData["Error"] != null)
|
|
{
|
|
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
|
@TempData["Error"]
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
|
</div>
|
|
}
|
|
|
|
<div class="table-responsive">
|
|
<table class="table table-striped">
|
|
<thead>
|
|
<tr>
|
|
<th>Name</th>
|
|
<th>Type</th>
|
|
<th>Platform Info</th>
|
|
<th>Status</th>
|
|
<th>Active Sessions</th>
|
|
<th>Total Revenue</th>
|
|
<th>Last Seen</th>
|
|
<th>Created</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
@foreach (var bot in Model)
|
|
{
|
|
<tr>
|
|
<td>
|
|
<strong>@bot.Name</strong>
|
|
@if (!string.IsNullOrEmpty(bot.PersonalityName))
|
|
{
|
|
<span class="badge bg-secondary ms-2">@bot.PersonalityName</span>
|
|
}
|
|
@if (bot.IsRemote)
|
|
{
|
|
<span class="badge bg-info ms-1" title="Remote bot at @bot.RemoteAddress:@bot.RemotePort">
|
|
<i class="fas fa-satellite-dish"></i> Remote
|
|
</span>
|
|
}
|
|
@if (!string.IsNullOrEmpty(bot.Description))
|
|
{
|
|
<br />
|
|
<small class="text-muted">@bot.Description</small>
|
|
}
|
|
</td>
|
|
<td>
|
|
<span class="badge bg-info">@bot.Type</span>
|
|
</td>
|
|
<td>
|
|
@if (!string.IsNullOrEmpty(bot.PlatformUsername))
|
|
{
|
|
<div>
|
|
<strong>@@@bot.PlatformUsername</strong>
|
|
@if (!string.IsNullOrEmpty(bot.PlatformDisplayName))
|
|
{
|
|
<br />
|
|
<small class="text-muted">@bot.PlatformDisplayName</small>
|
|
}
|
|
</div>
|
|
}
|
|
else
|
|
{
|
|
<span class="text-muted">Not configured</span>
|
|
}
|
|
</td>
|
|
<td>
|
|
@switch (bot.Status)
|
|
{
|
|
case LittleShop.Enums.BotStatus.Active:
|
|
<span class="badge bg-success">Active</span>
|
|
break;
|
|
case LittleShop.Enums.BotStatus.Inactive:
|
|
<span class="badge bg-secondary">Inactive</span>
|
|
break;
|
|
case LittleShop.Enums.BotStatus.Suspended:
|
|
<span class="badge bg-warning">Suspended</span>
|
|
break;
|
|
case LittleShop.Enums.BotStatus.Maintenance:
|
|
<span class="badge bg-info">Maintenance</span>
|
|
break;
|
|
default:
|
|
<span class="badge bg-dark">@bot.Status</span>
|
|
break;
|
|
}
|
|
@if (bot.IsRemote)
|
|
{
|
|
<br />
|
|
@switch (bot.DiscoveryStatus)
|
|
{
|
|
case "Configured":
|
|
<span class="badge bg-success small mt-1" title="Remote bot is fully configured">
|
|
<i class="fas fa-check"></i> Configured
|
|
</span>
|
|
break;
|
|
case "Initialized":
|
|
<span class="badge bg-info small mt-1" title="Remote bot initialized, awaiting config">
|
|
<i class="fas fa-clock"></i> Initialized
|
|
</span>
|
|
break;
|
|
case "Discovered":
|
|
<span class="badge bg-warning small mt-1" title="Remote bot discovered, needs setup">
|
|
<i class="fas fa-exclamation"></i> Needs Setup
|
|
</span>
|
|
break;
|
|
case "Offline":
|
|
case "Error":
|
|
<span class="badge bg-danger small mt-1" title="Remote bot is offline or errored">
|
|
<i class="fas fa-times"></i> @bot.DiscoveryStatus
|
|
</span>
|
|
break;
|
|
default:
|
|
<span class="badge bg-secondary small mt-1">@bot.DiscoveryStatus</span>
|
|
break;
|
|
}
|
|
}
|
|
</td>
|
|
<td>
|
|
<span class="badge bg-primary">@bot.ActiveSessions</span>
|
|
</td>
|
|
<td>$@bot.TotalRevenue.ToString("F2")</td>
|
|
<td>
|
|
@if (bot.LastSeenAt.HasValue)
|
|
{
|
|
<span title="@bot.LastSeenAt.Value.ToString("yyyy-MM-dd HH:mm:ss")">
|
|
@((DateTime.UtcNow - bot.LastSeenAt.Value).TotalMinutes < 5 ? "Online" : bot.LastSeenAt.Value.ToString("yyyy-MM-dd HH:mm"))
|
|
</span>
|
|
@if ((DateTime.UtcNow - bot.LastSeenAt.Value).TotalMinutes < 5)
|
|
{
|
|
<span class="text-success">●</span>
|
|
}
|
|
}
|
|
else
|
|
{
|
|
<span class="text-muted">Never</span>
|
|
}
|
|
</td>
|
|
<td>@bot.CreatedAt.ToString("yyyy-MM-dd")</td>
|
|
<td>
|
|
<div class="btn-group btn-group-sm" role="group">
|
|
<a href="/Admin/Bots/Details/@bot.Id" class="btn btn-outline-info" title="View Details">
|
|
<i class="bi bi-eye"></i>
|
|
</a>
|
|
<a href="/Admin/Bots/Edit/@bot.Id" class="btn btn-outline-primary" title="Edit">
|
|
<i class="bi bi-pencil"></i>
|
|
</a>
|
|
<a href="/Admin/Bots/Metrics/@bot.Id" class="btn btn-outline-success" title="View Metrics">
|
|
<i class="bi bi-graph-up"></i>
|
|
</a>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
@if (!Model.Any())
|
|
{
|
|
<div class="alert alert-info">
|
|
No bots have been registered yet. <a asp-action="Create">Register your first bot</a>.
|
|
</div>
|
|
} |