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:
@@ -272,4 +272,60 @@
|
||||
}
|
||||
});
|
||||
|
||||
// ========================================
|
||||
// SignalR — Real-time Activity Feed
|
||||
// ========================================
|
||||
|
||||
function initActivityHub() {
|
||||
var feed = document.getElementById('activity-feed');
|
||||
if (!feed) return; // Not on the Analytics page
|
||||
|
||||
var connection = new signalR.HubConnectionBuilder()
|
||||
.withUrl('/hubs/activity')
|
||||
.withAutomaticReconnect()
|
||||
.build();
|
||||
|
||||
connection.on('NewActivity', function(data) {
|
||||
// Remove the empty-state placeholder if present
|
||||
var empty = document.getElementById('activity-empty');
|
||||
if (empty) empty.remove();
|
||||
|
||||
var item = document.createElement('div');
|
||||
item.className = 'activity-item activity-item--new';
|
||||
|
||||
var displayText = data.url || data.windowTitle || '';
|
||||
// Escape HTML to prevent XSS
|
||||
var div = document.createElement('div');
|
||||
div.textContent = data.appName || '';
|
||||
var safeApp = div.innerHTML;
|
||||
div.textContent = displayText;
|
||||
var safeTitle = div.innerHTML;
|
||||
|
||||
item.innerHTML =
|
||||
'<span class="activity-dot" style="background: var(--color-accent)"></span>' +
|
||||
'<div class="activity-line"></div>' +
|
||||
'<div class="activity-info">' +
|
||||
'<span class="activity-app">' + safeApp + '</span>' +
|
||||
'<span class="activity-title">' + safeTitle + '</span>' +
|
||||
'<span class="activity-time">just now</span>' +
|
||||
'</div>';
|
||||
|
||||
feed.insertBefore(item, feed.firstChild);
|
||||
|
||||
// Cap visible items at 100 to prevent memory bloat
|
||||
var items = feed.querySelectorAll('.activity-item');
|
||||
if (items.length > 100) {
|
||||
items[items.length - 1].remove();
|
||||
}
|
||||
});
|
||||
|
||||
connection.start().catch(function(err) {
|
||||
console.error('SignalR connection error:', err);
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
initActivityHub();
|
||||
});
|
||||
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user