feat: added frontend exception notifications

This commit is contained in:
Mose Müller 2023-08-03 15:18:32 +02:00
parent 85a171c33e
commit b30295d840
3 changed files with 53 additions and 17 deletions

View File

@ -16,3 +16,6 @@ input.instantUpdate {
.notificationToast { .notificationToast {
background-color: rgba(114, 214, 253, 0.5) !important; background-color: rgba(114, 214, 253, 0.5) !important;
} }
.exceptionToast {
background-color: rgba(216, 41, 18, 0.678) !important;
}

View File

@ -20,9 +20,12 @@ type State = DataServiceJSON | null;
type Action = type Action =
| { type: 'SET_DATA'; data: DataServiceJSON } | { type: 'SET_DATA'; data: DataServiceJSON }
| { type: 'UPDATE_ATTRIBUTE'; parent_path: string; name: string; value: ValueType }; | { type: 'UPDATE_ATTRIBUTE'; parent_path: string; name: string; value: ValueType };
type NotificationElement = { type UpdateNotification = {
data: { parent_path: string; name: string; value: object }; data: { parent_path: string; name: string; value: object };
}; };
type ExceptionNotification = {
data: { exception: string; type: string };
};
/** /**
* A function to update a specific property in a deeply nested object. * A function to update a specific property in a deeply nested object.
@ -128,7 +131,7 @@ const App = () => {
const handleCloseSettings = () => setShowSettings(false); const handleCloseSettings = () => setShowSettings(false);
const handleShowSettings = () => setShowSettings(true); const handleShowSettings = () => setShowSettings(true);
function onNotify(value: NotificationElement) { function onNotify(value: UpdateNotification) {
dispatch({ dispatch({
type: 'UPDATE_ATTRIBUTE', type: 'UPDATE_ATTRIBUTE',
parent_path: value.data.parent_path, parent_path: value.data.parent_path,
@ -142,6 +145,15 @@ const App = () => {
setNotifications((prevNotifications) => [newNotification, ...prevNotifications]); setNotifications((prevNotifications) => [newNotification, ...prevNotifications]);
} }
function onException(value: ExceptionNotification) {
const newNotification = {
type: 'exception',
id: Math.random(),
text: `${value.data.type}: ${value.data.exception}.`
};
setNotifications((prevNotifications) => [newNotification, ...prevNotifications]);
}
useEffect(() => { useEffect(() => {
// Fetch data from the API when the component mounts // Fetch data from the API when the component mounts
fetch(`http://${hostname}:${port}/service-properties`) fetch(`http://${hostname}:${port}/service-properties`)
@ -149,9 +161,11 @@ const App = () => {
.then((data: DataServiceJSON) => dispatch({ type: 'SET_DATA', data })); .then((data: DataServiceJSON) => dispatch({ type: 'SET_DATA', data }));
socket.on('notify', onNotify); socket.on('notify', onNotify);
socket.on('exception', onException);
return () => { return () => {
socket.off('notify', onNotify); socket.off('notify', onNotify);
socket.off('exception', onException);
}; };
}, []); }, []);
@ -175,7 +189,11 @@ const App = () => {
style={{ position: 'fixed' }}> style={{ position: 'fixed' }}>
{notifications.map((notification) => ( {notifications.map((notification) => (
<Toast <Toast
className="notificationToast" className={
notification.type === 'exception'
? 'exceptionToast'
: 'notificationToast'
}
key={notification.id} key={notification.id}
onClose={() => { onClose={() => {
removeNotificationById(notification.id); removeNotificationById(notification.id);
@ -184,13 +202,25 @@ const App = () => {
removeNotificationById(notification.id); removeNotificationById(notification.id);
}} }}
onMouseLeave={() => { onMouseLeave={() => {
// For exception type notifications, do not dismiss on mouse leave
if (notification.type !== 'exception') {
removeNotificationById(notification.id); removeNotificationById(notification.id);
}
}} }}
show={true} show={true}
autohide autohide={notification.type !== 'exception'} // Do not autohide for 'exception' type notifications
delay={2000}> delay={notification.type === 'exception' ? 0 : 2000} // No delay for 'exception' type notifications
<Toast.Header closeButton={false} className="notificationToast"> >
<strong className="mr-auto">Notification</strong> <Toast.Header
closeButton={false}
className={
notification.type === 'exception'
? 'exceptionToast'
: 'notificationToast'
}>
<strong className="mr-auto">
{notification.type === 'exception' ? 'Exception' : 'Notification'}
</strong>
</Toast.Header> </Toast.Header>
<Toast.Body>{notification.text}</Toast.Body> <Toast.Body>{notification.text}</Toast.Body>
</Toast> </Toast>

View File

@ -396,6 +396,7 @@ class Server:
if self._enable_web: if self._enable_web:
async def emit_exception() -> None: async def emit_exception() -> None:
try:
await self._wapi.sio.emit( # type: ignore await self._wapi.sio.emit( # type: ignore
"exception", "exception",
{ {
@ -405,6 +406,8 @@ class Server:
} }
}, },
) )
except Exception as e:
logger.warning(f"Failed to send notification: {e}")
loop.create_task(emit_exception()) loop.create_task(emit_exception())
else: else: