feat(web): add real-time activity feed via SignalR
- Add ActivityHub and wire up SignalR in Program.cs - Broadcast new context events from ContextController - Connect SignalR client on Analytics page for live feed updates - Restructure activity feed HTML to support live prepending - Add slide-in animation for new activity items - Update CORS to allow credentials for SignalR Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -87,35 +87,32 @@
|
||||
<section>
|
||||
<h2 class="section-title">Recent Activity</h2>
|
||||
<div class="surface">
|
||||
@if (Model.ActivityItems.Count == 0)
|
||||
{
|
||||
<div class="search-empty">No activity recorded in this time range.</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div id="activity-feed" class="activity-feed">
|
||||
@foreach (var item in Model.ActivityItems)
|
||||
{
|
||||
<div class="activity-item">
|
||||
<span class="activity-dot" style="background: var(--color-accent)"></span>
|
||||
<div class="activity-line"></div>
|
||||
<div class="activity-info">
|
||||
<span class="activity-app">@item.AppName</span>
|
||||
<span class="activity-title">@(string.IsNullOrEmpty(item.Url) ? item.WindowTitle : item.Url)</span>
|
||||
<span class="activity-time">@AnalyticsModel.FormatRelativeTime(item.Timestamp)</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@if (Model.TotalActivityCount > Model.ActivityItems.Count)
|
||||
<div id="activity-feed" class="activity-feed">
|
||||
@if (Model.ActivityItems.Count == 0)
|
||||
{
|
||||
<button class="btn btn--ghost btn--full"
|
||||
hx-get="/analytics?handler=ActivityFeed&minutes=@Model.Minutes&taskId=@Model.TaskId&offset=@Model.ActivityItems.Count"
|
||||
hx-target="#activity-feed"
|
||||
hx-swap="beforeend">
|
||||
Load more (@(Model.TotalActivityCount - Model.ActivityItems.Count) remaining)
|
||||
</button>
|
||||
<div class="search-empty" id="activity-empty">No activity recorded in this time range.</div>
|
||||
}
|
||||
@foreach (var item in Model.ActivityItems)
|
||||
{
|
||||
<div class="activity-item">
|
||||
<span class="activity-dot" style="background: var(--color-accent)"></span>
|
||||
<div class="activity-line"></div>
|
||||
<div class="activity-info">
|
||||
<span class="activity-app">@item.AppName</span>
|
||||
<span class="activity-title">@(string.IsNullOrEmpty(item.Url) ? item.WindowTitle : item.Url)</span>
|
||||
<span class="activity-time">@AnalyticsModel.FormatRelativeTime(item.Timestamp)</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@if (Model.TotalActivityCount > Model.ActivityItems.Count)
|
||||
{
|
||||
<button class="btn btn--ghost btn--full"
|
||||
hx-get="/analytics?handler=ActivityFeed&minutes=@Model.Minutes&taskId=@Model.TaskId&offset=@Model.ActivityItems.Count"
|
||||
hx-target="#activity-feed"
|
||||
hx-swap="beforeend">
|
||||
Load more (@(Model.TotalActivityCount - Model.ActivityItems.Count) remaining)
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -78,6 +78,7 @@
|
||||
<script src="~/lib/htmx.min.js"></script>
|
||||
<script src="~/lib/Sortable.min.js"></script>
|
||||
<script src="~/lib/chart.umd.min.js"></script>
|
||||
<script src="~/lib/signalr.min.js"></script>
|
||||
<script src="~/js/app.js"></script>
|
||||
@await RenderSectionAsync("Scripts", required: false)
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user