diff --git a/ui_src/src/App.js b/ui_src/src/App.js index a4548a7d0..0da13ab08 100644 --- a/ui_src/src/App.js +++ b/ui_src/src/App.js @@ -14,9 +14,11 @@ import './App.scss'; import { Switch, Route, withRouter } from 'react-router-dom'; import React, { useCallback, useContext, useEffect, useRef, useState } from 'react'; +import { JSONCodec, StringCodec, connect } from 'nats.ws'; import { useMediaQuery } from 'react-responsive'; -import { connect } from 'nats.ws'; -import { message } from 'antd'; +import { useHistory } from 'react-router-dom'; +import { message, notification } from 'antd'; +import { Redirect } from 'react-router-dom'; import { LOCAL_STORAGE_ACCOUNT_ID, @@ -28,23 +30,27 @@ import { } from './const/localStorageConsts'; import { CLOUD_URL, ENVIRONMENT, HANDLE_REFRESH_INTERVAL, WS_PREFIX, WS_SERVER_URL_PRODUCTION } from './config'; import { handleRefreshTokenRequest, httpRequest } from './services/http'; +import infoNotificationIcon from './assets/images/infoNotificationIcon.svg'; +import successIcon from './assets/images/successIcon.svg'; +import close from './assets/images/closeNotification.svg'; import StationOverview from './domain/stationOverview'; +import errorIcon from './assets/images/errorIcon.svg'; import MessageJourney from './domain/messageJourney'; -import { isCloud } from './services/valueConvertor'; import Administration from './domain/administration'; +import { ApiEndpoints } from './const/apiEndpoints'; +import { isCloud } from './services/valueConvertor'; +import warnIcon from './assets/images/warnIcon.svg'; import AppWrapper from './components/appWrapper'; import StationsList from './domain/stationsList'; import SchemaManagment from './domain/schema'; -import { useHistory } from 'react-router-dom'; -import { Redirect } from 'react-router-dom'; import PrivateRoute from './PrivateRoute'; +import AuthService from './services/auth'; import Overview from './domain/overview'; +import Loader from './components/loader'; import { Context } from './hooks/store'; import Profile from './domain/profile'; import pathDomains from './router'; import Users from './domain/users'; -import { ApiEndpoints } from './const/apiEndpoints'; -import AuthService from './services/auth'; let SysLogs = undefined; let Login = undefined; @@ -65,9 +71,14 @@ const App = withRouter((props) => { const firebase_id_token = urlParams.get('firebase_id_token'); const firebase_organization_id = urlParams.get('firebase_organization_id'); const [cloudLogedIn, setCloudLogedIn] = useState(isCloud() ? false : true); + const [persistedNotifications, setPersistedNotifications] = useState(() => { + const storedNotifications = JSON.parse(localStorage.getItem('persistedNotifications')); + return storedNotifications || []; + }); + const [displayedNotifications, setDisplayedNotifications] = useState([]); - const ref = useRef(); - ref.current = cloudLogedIn; + const stateRef = useRef([]); + stateRef.current = [cloudLogedIn, persistedNotifications]; const handleLoginWithToken = async () => { try { @@ -133,7 +144,7 @@ const App = withRouter((props) => { }, [isMobile]); const handleRefresh = useCallback(async (firstTime) => { - if (window.location.pathname === pathDomains.login || (firebase_id_token !== null && !ref.current)) { + if (window.location.pathname === pathDomains.login || (firebase_id_token !== null && !stateRef.current[0])) { return; } else if (localStorage.getItem(LOCAL_STORAGE_TOKEN)) { const ws_port = localStorage.getItem(LOCAL_STORAGE_WS_PORT); @@ -189,8 +200,115 @@ const App = withRouter((props) => { }; }, [handleRefresh, setAuthCheck]); + useEffect(() => { + const sc = StringCodec(); + const jc = JSONCodec(); + let sub; + const subscribeToNotifications = async () => { + try { + const rawBrokerName = await state.socket?.request(`$memphis_ws_subs.get_system_messages`, sc.encode('SUB')); + if (rawBrokerName) { + const brokerName = JSON.parse(sc.decode(rawBrokerName?._rdata))['name']; + sub = state.socket?.subscribe(`$memphis_ws_pubs.get_system_messages.${brokerName}`); + listenForUpdates(); + } + } catch (err) { + console.error('Error subscribing to overview data:', err); + } + }; + + const listenForUpdates = async () => { + try { + if (sub) { + for await (const msg of sub) { + let data = jc.decode(msg.data); + const uniqueNewNotifications = data.filter((newNotification) => { + return !stateRef.current[1].some((existingNotification) => existingNotification.id === newNotification.id); + }); + setPersistedNotifications((prevPersistedNotifications) => [...prevPersistedNotifications, ...uniqueNewNotifications]); + localStorage.setItem('persistedNotifications', JSON.stringify([...stateRef.current[1], ...uniqueNewNotifications])); + } + } + } catch (err) { + console.error('Error receiving overview data updates:', err); + } + }; + + isCloud() && subscribeToNotifications(); + + return () => { + if (sub) { + try { + sub.unsubscribe(); + } catch (err) { + console.error('Error unsubscribing from overview data:', err); + } + } + }; + }, [state.socket]); + + const notificationHandler = (id, type, message, duration) => { + const defaultAntdField = { + className: 'notification-wrapper', + closeIcon: close, + message: 'System Message', + onClose: () => { + const updatedNotifications = stateRef.current[1].map((n) => (n.id === id ? { ...n, read: true } : n)); + setPersistedNotifications(updatedNotifications); + localStorage.setItem('persistedNotifications', JSON.stringify(updatedNotifications)); + } + }; + switch (type) { + case 'info': + notification.info({ + ...defaultAntdField, + icon: info, + description: message, + duration: duration + }); + break; + case 'warning': + notification.warning({ + ...defaultAntdField, + + icon: warn, + description: message, + duration: duration + }); + break; + case 'error': + notification.error({ + ...defaultAntdField, + icon: error, + description: message, + duration: duration + }); + break; + case 'success': + notification.success({ + ...defaultAntdField, + icon: success, + description: message, + duration: duration + }); + break; + default: + break; + } + }; + + useEffect(() => { + stateRef.current[1].forEach((notification) => { + if (!displayedNotifications.includes(notification.id) && !notification.read) { + notificationHandler(notification.id, notification.message_type, notification.message_payload, 0); + setDisplayedNotifications((prevDisplayedNotifications) => [...prevDisplayedNotifications, notification.id]); + } + }); + }, [stateRef.current[1]]); + return (
+ {!cloudLogedIn && }
{' '} {!authCheck && diff --git a/ui_src/src/App.scss b/ui_src/src/App.scss index aa5eae054..99ea45dbc 100644 --- a/ui_src/src/App.scss +++ b/ui_src/src/App.scss @@ -159,4 +159,19 @@ pre { } .open { transform: rotate(0deg); +} + +.notification-wrapper{ + border-radius: 12px; + border: 1px solid var(--gray-200, #EAECF0); + box-shadow: 0px 4px 6px -2px rgba(16, 24, 40, 0.03), 0px 12px 16px -4px rgba(16, 24, 40, 0.08); + .ant-notification-notice-message{ + font-family: 'InterSemiBold'; + font-size: 16px; + } + .ant-notification-notice-description{ + font-family: 'Inter'; + font-size: 14px; + color: #475467; + } } \ No newline at end of file diff --git a/ui_src/src/assets/images/closeNotification.svg b/ui_src/src/assets/images/closeNotification.svg new file mode 100644 index 000000000..3385614d8 --- /dev/null +++ b/ui_src/src/assets/images/closeNotification.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui_src/src/assets/images/errorIcon.svg b/ui_src/src/assets/images/errorIcon.svg new file mode 100644 index 000000000..bc560c40f --- /dev/null +++ b/ui_src/src/assets/images/errorIcon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ui_src/src/assets/images/infoNotificationIcon.svg b/ui_src/src/assets/images/infoNotificationIcon.svg new file mode 100644 index 000000000..23b7de4bf --- /dev/null +++ b/ui_src/src/assets/images/infoNotificationIcon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ui_src/src/assets/images/successIcon.svg b/ui_src/src/assets/images/successIcon.svg new file mode 100644 index 000000000..02171bf7a --- /dev/null +++ b/ui_src/src/assets/images/successIcon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ui_src/src/assets/images/warnIcon.svg b/ui_src/src/assets/images/warnIcon.svg new file mode 100644 index 000000000..93c3bf731 --- /dev/null +++ b/ui_src/src/assets/images/warnIcon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ui_src/src/components/createStationForm/index.js b/ui_src/src/components/createStationForm/index.js index 8c6fd3bf6..87fbd8ba2 100644 --- a/ui_src/src/components/createStationForm/index.js +++ b/ui_src/src/components/createStationForm/index.js @@ -176,26 +176,26 @@ const CreateStationForm = ({ createStationFormRef, getStartedStateRef, finishUpd break; } setActualPods(replicas); - } catch (error) { } + } catch (error) {} }; const getAllSchemas = async () => { try { const data = await httpRequest('GET', ApiEndpoints.GET_ALL_SCHEMAS); setSchemas(data); - } catch (error) { } + } catch (error) {} }; const getIntegration = async () => { try { const data = await httpRequest('GET', `${ApiEndpoints.GET_INTEGRATION_DETAILS}?name=s3`); setIntegrateValue(data); - } catch (error) { } + } catch (error) {} }; const createStation = async (bodyRequest) => { try { - getStarted && setLoading(true); + setLoading(true); const data = await httpRequest('POST', ApiEndpoints.CREATE_STATION, bodyRequest); if (data) { if (!getStarted) history.push(`${pathDomains.stations}/${data.name}`); @@ -203,7 +203,7 @@ const CreateStationForm = ({ createStationFormRef, getStartedStateRef, finishUpd } } catch (error) { } finally { - getStarted && setLoading(false); + setLoading(false); } }; @@ -287,7 +287,7 @@ const CreateStationForm = ({ createStationFormRef, getStartedStateRef, finishUpd
)}
- {!isCloud() && + {!isCloud() && (
- } + )}
diff --git a/ui_src/src/domain/overview/index.js b/ui_src/src/domain/overview/index.js index b2b8ace55..888fe71ef 100644 --- a/ui_src/src/domain/overview/index.js +++ b/ui_src/src/domain/overview/index.js @@ -214,12 +214,12 @@ function OverView() {

Account ID :

{localStorage.getItem(LOCAL_STORAGE_ACCOUNT_ID)} - +

Broker Hostname :

{host} - +
)} @@ -291,7 +291,7 @@ function OverView() { open={open} isLoading={creatingProsessd} > - setCreatingProsessd(e)} /> + setCreatingProsessd(e)} />
); diff --git a/ui_src/src/domain/overview/schemaverse/index.js b/ui_src/src/domain/overview/schemaverse/index.js index beb49ede6..660695b63 100644 --- a/ui_src/src/domain/overview/schemaverse/index.js +++ b/ui_src/src/domain/overview/schemaverse/index.js @@ -35,12 +35,12 @@ const Schemaverse = () => {
-

Total Schemas

+

Total schemas

{state?.monitor_data?.schemas_details?.total_schemas}

-

Enforced Schemas

+

Enforced schemas

{state?.monitor_data?.schemas_details?.enforced_schemas}

diff --git a/ui_src/src/domain/stationOverview/stationObservabilty/messages/index.js b/ui_src/src/domain/stationOverview/stationObservabilty/messages/index.js index ae7760483..206b86cfc 100644 --- a/ui_src/src/domain/stationOverview/stationObservabilty/messages/index.js +++ b/ui_src/src/domain/stationOverview/stationObservabilty/messages/index.js @@ -150,22 +150,22 @@ const Messages = () => { }; const handleResend = async () => { - setResendProcced(true); + // setResendProcced(true); try { await httpRequest('POST', `${ApiEndpoints.RESEND_POISON_MESSAGE_JOURNEY}`, { poison_message_ids: isCheck, station_name: stationName }); - setTimeout(() => { - setResendProcced(false); - message.success({ - key: 'memphisSuccessMessage', - content: isCheck.length === 1 ? 'The message was sent successfully' : 'The messages were sent successfully', - duration: 5, - style: { cursor: 'pointer' }, - onClick: () => message.destroy('memphisSuccessMessage') - }); - setIsCheck([]); - }, 1500); + // setTimeout(() => { + // setResendProcced(false); + // message.success({ + // key: 'memphisSuccessMessage', + // content: isCheck.length === 1 ? 'The message was sent successfully' : 'The messages were sent successfully', + // duration: 5, + // style: { cursor: 'pointer' }, + // onClick: () => message.destroy('memphisSuccessMessage') + // }); + // setIsCheck([]); + // }, 1500); } catch (error) { - setResendProcced(false); + // setResendProcced(false); } }; @@ -256,28 +256,27 @@ const Messages = () => {