Spaces:
Paused
Paused
Rafael Uzarowski commited on
feat: frontend-only notifications and test button for them
Browse files
webui/components/notifications/notification-icons.html
CHANGED
|
@@ -29,6 +29,13 @@
|
|
| 29 |
title="Create Test Notification">
|
| 30 |
<div class="notification-icon">🧪</div>
|
| 31 |
</button>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 32 |
</div>
|
| 33 |
</body>
|
| 34 |
</html>
|
|
|
|
| 29 |
title="Create Test Notification">
|
| 30 |
<div class="notification-icon">🧪</div>
|
| 31 |
</button>
|
| 32 |
+
|
| 33 |
+
<!-- Test Frontend Error Button (for development) -->
|
| 34 |
+
<button class="notification-test-button frontend-error"
|
| 35 |
+
@click="toastFrontendError('Backend connection failed', 'Connection Error')"
|
| 36 |
+
title="Test Frontend Error Toast">
|
| 37 |
+
<div class="notification-icon">⚠️</div>
|
| 38 |
+
</button>
|
| 39 |
</div>
|
| 40 |
</body>
|
| 41 |
</html>
|
webui/css/notification.css
CHANGED
|
@@ -109,6 +109,17 @@
|
|
| 109 |
font-size: 1.2rem;
|
| 110 |
}
|
| 111 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 112 |
/* Light Mode Styles */
|
| 113 |
.light-mode .notification-toggle {
|
| 114 |
background: rgba(0, 0, 0, 0.05);
|
|
@@ -140,6 +151,16 @@
|
|
| 140 |
border-color: rgba(33, 150, 243, 0.5);
|
| 141 |
}
|
| 142 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 143 |
/* Animations */
|
| 144 |
@keyframes pulse {
|
| 145 |
0%, 100% { transform: scale(1); }
|
|
|
|
| 109 |
font-size: 1.2rem;
|
| 110 |
}
|
| 111 |
|
| 112 |
+
/* Frontend Error Test Button - distinct orange/red styling */
|
| 113 |
+
.notification-test-button.frontend-error {
|
| 114 |
+
background: rgba(255, 152, 0, 0.15);
|
| 115 |
+
border-color: rgba(255, 152, 0, 0.4);
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
.notification-test-button.frontend-error:hover {
|
| 119 |
+
background: rgba(255, 152, 0, 0.25);
|
| 120 |
+
border-color: rgba(255, 152, 0, 0.6);
|
| 121 |
+
}
|
| 122 |
+
|
| 123 |
/* Light Mode Styles */
|
| 124 |
.light-mode .notification-toggle {
|
| 125 |
background: rgba(0, 0, 0, 0.05);
|
|
|
|
| 151 |
border-color: rgba(33, 150, 243, 0.5);
|
| 152 |
}
|
| 153 |
|
| 154 |
+
.light-mode .notification-test-button.frontend-error {
|
| 155 |
+
background: rgba(255, 152, 0, 0.15);
|
| 156 |
+
border-color: rgba(255, 152, 0, 0.4);
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
.light-mode .notification-test-button.frontend-error:hover {
|
| 160 |
+
background: rgba(255, 152, 0, 0.25);
|
| 161 |
+
border-color: rgba(255, 152, 0, 0.6);
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
/* Animations */
|
| 165 |
@keyframes pulse {
|
| 166 |
0%, 100% { transform: scale(1); }
|
webui/js/notificationStore.js
CHANGED
|
@@ -366,9 +366,96 @@ const model = {
|
|
| 366 |
// Legacy method for backward compatibility
|
| 367 |
toggleNotifications() {
|
| 368 |
this.openModal();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 369 |
}
|
| 370 |
};
|
| 371 |
|
| 372 |
// Create and export the store
|
| 373 |
const store = createStore("notificationStore", model);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 374 |
export { store };
|
|
|
|
| 366 |
// Legacy method for backward compatibility
|
| 367 |
toggleNotifications() {
|
| 368 |
this.openModal();
|
| 369 |
+
},
|
| 370 |
+
|
| 371 |
+
// NEW: Add frontend-only toast directly to stack (for connection errors, etc.)
|
| 372 |
+
addFrontendToast(type, message, title = "", display_time = 5) {
|
| 373 |
+
const timestamp = new Date().toISOString();
|
| 374 |
+
const notification = {
|
| 375 |
+
id: `frontend-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
| 376 |
+
type: type,
|
| 377 |
+
title: title,
|
| 378 |
+
message: message,
|
| 379 |
+
detail: "",
|
| 380 |
+
timestamp: timestamp,
|
| 381 |
+
display_time: display_time,
|
| 382 |
+
read: false,
|
| 383 |
+
frontend: true // Mark as frontend-only
|
| 384 |
+
};
|
| 385 |
+
|
| 386 |
+
// Create toast object with auto-dismiss timer
|
| 387 |
+
const toast = {
|
| 388 |
+
...notification,
|
| 389 |
+
toastId: `toast-${notification.id}`,
|
| 390 |
+
addedAt: Date.now(),
|
| 391 |
+
autoRemoveTimer: null
|
| 392 |
+
};
|
| 393 |
+
|
| 394 |
+
// Add to bottom of stack (newest at bottom)
|
| 395 |
+
this.toastStack.push(toast);
|
| 396 |
+
|
| 397 |
+
// Enforce max stack limit (remove oldest from top)
|
| 398 |
+
if (this.toastStack.length > this.maxToastStack) {
|
| 399 |
+
const removed = this.toastStack.shift(); // Remove from top
|
| 400 |
+
if (removed.autoRemoveTimer) {
|
| 401 |
+
clearTimeout(removed.autoRemoveTimer);
|
| 402 |
+
}
|
| 403 |
+
}
|
| 404 |
+
|
| 405 |
+
// Set auto-dismiss timer
|
| 406 |
+
toast.autoRemoveTimer = setTimeout(() => {
|
| 407 |
+
this.removeFromToastStack(toast.toastId);
|
| 408 |
+
}, notification.display_time * 1000);
|
| 409 |
+
|
| 410 |
+
console.log(`Frontend toast added: ${notification.type} - ${notification.message}`);
|
| 411 |
+
return notification.id;
|
| 412 |
+
},
|
| 413 |
+
|
| 414 |
+
// NEW: Convenience methods for frontend-only notifications
|
| 415 |
+
frontendError(message, title = "Connection Error", display_time = 8) {
|
| 416 |
+
return this.addFrontendToast('error', message, title, display_time);
|
| 417 |
+
},
|
| 418 |
+
|
| 419 |
+
frontendWarning(message, title = "Warning", display_time = 5) {
|
| 420 |
+
return this.addFrontendToast('warning', message, title, display_time);
|
| 421 |
+
},
|
| 422 |
+
|
| 423 |
+
frontendInfo(message, title = "Info", display_time = 3) {
|
| 424 |
+
return this.addFrontendToast('info', message, title, display_time);
|
| 425 |
}
|
| 426 |
};
|
| 427 |
|
| 428 |
// Create and export the store
|
| 429 |
const store = createStore("notificationStore", model);
|
| 430 |
+
|
| 431 |
+
// NEW: Global function for frontend error toasts (replaces toastFetchError)
|
| 432 |
+
window.toastFrontendError = function(message, title = "Connection Error") {
|
| 433 |
+
if (window.Alpine && window.Alpine.store && window.Alpine.store('notificationStore')) {
|
| 434 |
+
return window.Alpine.store('notificationStore').frontendError(message, title);
|
| 435 |
+
} else {
|
| 436 |
+
// Fallback if Alpine/store not ready
|
| 437 |
+
console.error('Frontend Error:', title, '-', message);
|
| 438 |
+
return null;
|
| 439 |
+
}
|
| 440 |
+
};
|
| 441 |
+
|
| 442 |
+
// NEW: Additional global convenience functions
|
| 443 |
+
window.toastFrontendWarning = function(message, title = "Warning") {
|
| 444 |
+
if (window.Alpine && window.Alpine.store && window.Alpine.store('notificationStore')) {
|
| 445 |
+
return window.Alpine.store('notificationStore').frontendWarning(message, title);
|
| 446 |
+
} else {
|
| 447 |
+
console.warn('Frontend Warning:', title, '-', message);
|
| 448 |
+
return null;
|
| 449 |
+
}
|
| 450 |
+
};
|
| 451 |
+
|
| 452 |
+
window.toastFrontendInfo = function(message, title = "Info") {
|
| 453 |
+
if (window.Alpine && window.Alpine.store && window.Alpine.store('notificationStore')) {
|
| 454 |
+
return window.Alpine.store('notificationStore').frontendInfo(message, title);
|
| 455 |
+
} else {
|
| 456 |
+
console.log('Frontend Info:', title, '-', message);
|
| 457 |
+
return null;
|
| 458 |
+
}
|
| 459 |
+
};
|
| 460 |
+
|
| 461 |
export { store };
|