diff --git a/src/aggregator/web/index.html b/src/aggregator/web/index.html
index a511fb8..a6f14eb 100644
--- a/src/aggregator/web/index.html
+++ b/src/aggregator/web/index.html
@@ -537,19 +537,77 @@
setTimeout(() => toast.remove(), 3000);
}
+ let usePolling = false;
+ let pollInterval = null;
+
+ // HTTP fallback to load initial data
+ async function loadDataViaHTTP() {
+ try {
+ const [projectsRes, tasksRes] = await Promise.all([
+ fetch('/api/projects'),
+ fetch('/api/tasks')
+ ]);
+ if (projectsRes.ok && tasksRes.ok) {
+ projects = await projectsRes.json();
+ tasks = await tasksRes.json();
+ renderProjects();
+ renderTasks();
+ updateProjectDropdown();
+ return true;
+ }
+ } catch (e) {
+ console.error('HTTP load failed:', e);
+ }
+ return false;
+ }
+
+ // Start polling for updates (fallback when WebSocket unavailable)
+ function startPolling() {
+ if (pollInterval) return;
+ usePolling = true;
+ document.getElementById('status-badge').textContent = 'Polling';
+ pollInterval = setInterval(loadDataViaHTTP, 5000);
+ }
+
function connectWebSocket() {
const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
ws = new WebSocket(`${protocol}//${location.host}`);
+ // Timeout - if WS doesn't connect in 5s, fall back to HTTP
+ const wsTimeout = setTimeout(async () => {
+ if (ws.readyState !== WebSocket.OPEN) {
+ console.log('WebSocket timeout, falling back to HTTP');
+ ws.close();
+ await loadDataViaHTTP();
+ startPolling();
+ }
+ }, 5000);
+
ws.onopen = () => {
+ clearTimeout(wsTimeout);
+ usePolling = false;
+ if (pollInterval) {
+ clearInterval(pollInterval);
+ pollInterval = null;
+ }
document.getElementById('status-badge').textContent = 'Live';
document.getElementById('status-badge').classList.remove('offline');
};
+ ws.onerror = async () => {
+ clearTimeout(wsTimeout);
+ console.log('WebSocket error, falling back to HTTP');
+ await loadDataViaHTTP();
+ startPolling();
+ };
+
ws.onclose = () => {
- document.getElementById('status-badge').textContent = 'Reconnecting...';
+ clearTimeout(wsTimeout);
+ document.getElementById('status-badge').textContent = usePolling ? 'Polling' : 'Reconnecting...';
document.getElementById('status-badge').classList.add('offline');
- setTimeout(connectWebSocket, 3000);
+ if (!usePolling) {
+ setTimeout(connectWebSocket, 3000);
+ }
};
ws.onmessage = (event) => {