mirror of
https://github.com/Telecominfraproject/wlan-cloud-ucentralgw-ui.git
synced 2025-11-02 11:47:54 +00:00
2.6.20: reboot/blink/trace UI fixes, now using global websocket to update UI and notify user on device connection/disconnection
This commit is contained in:
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "ucentral-client",
|
"name": "ucentral-client",
|
||||||
"version": "2.6.14",
|
"version": "2.6.20",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "ucentral-client",
|
"name": "ucentral-client",
|
||||||
"version": "2.6.14",
|
"version": "2.6.20",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@coreui/coreui": "^3.4.0",
|
"@coreui/coreui": "^3.4.0",
|
||||||
"@coreui/icons": "^2.0.1",
|
"@coreui/icons": "^2.0.1",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ucentral-client",
|
"name": "ucentral-client",
|
||||||
"version": "2.6.14",
|
"version": "2.6.20",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@coreui/coreui": "^3.4.0",
|
"@coreui/coreui": "^3.4.0",
|
||||||
"@coreui/icons": "^2.0.1",
|
"@coreui/icons": "^2.0.1",
|
||||||
|
|||||||
@@ -62,12 +62,14 @@ const BlinkModal = ({ show, toggleModal }) => {
|
|||||||
{ headers },
|
{ headers },
|
||||||
)
|
)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
addToast({
|
if (chosenPattern !== 'blink') {
|
||||||
title: t('common.success'),
|
addToast({
|
||||||
body: t('commands.command_success'),
|
title: t('common.success'),
|
||||||
color: 'success',
|
body: t('commands.command_success'),
|
||||||
autohide: true,
|
color: 'success',
|
||||||
});
|
autohide: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
toggleModal();
|
toggleModal();
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
@@ -145,8 +147,10 @@ const BlinkModal = ({ show, toggleModal }) => {
|
|||||||
</CModalBody>
|
</CModalBody>
|
||||||
<CModalFooter>
|
<CModalFooter>
|
||||||
<LoadingButton
|
<LoadingButton
|
||||||
label={t('blink.set_leds')}
|
label={t('common.submit')}
|
||||||
isLoadingLabel={t('common.loading_ellipsis')}
|
isLoadingLabel={
|
||||||
|
chosenPattern === 'blink' ? 'LEDs are blinking... ' : t('common.loading_ellipsis')
|
||||||
|
}
|
||||||
isLoading={waiting}
|
isLoading={waiting}
|
||||||
action={doAction}
|
action={doAction}
|
||||||
block={false}
|
block={false}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import PropTypes from 'prop-types';
|
|||||||
import { useAuth, useToast, useToggle } from 'ucentral-libs';
|
import { useAuth, useToast, useToggle } from 'ucentral-libs';
|
||||||
import axiosInstance from 'utils/axiosInstance';
|
import axiosInstance from 'utils/axiosInstance';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useGlobalWebSocket } from 'contexts/WebSocketProvider';
|
||||||
import Modal from './Modal';
|
import Modal from './Modal';
|
||||||
|
|
||||||
const DeviceFirmwareModal = ({
|
const DeviceFirmwareModal = ({
|
||||||
@@ -19,6 +20,7 @@ const DeviceFirmwareModal = ({
|
|||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [firmwareVersions, setFirmwareVersions] = useState([]);
|
const [firmwareVersions, setFirmwareVersions] = useState([]);
|
||||||
const [keepRedirector, toggleKeepRedirector, setKeepRedirector] = useToggle(true);
|
const [keepRedirector, toggleKeepRedirector, setKeepRedirector] = useToggle(true);
|
||||||
|
const { addDeviceListener } = useGlobalWebSocket();
|
||||||
|
|
||||||
const getPartialFirmware = async (offset) => {
|
const getPartialFirmware = async (offset) => {
|
||||||
const headers = {
|
const headers = {
|
||||||
@@ -90,6 +92,17 @@ const DeviceFirmwareModal = ({
|
|||||||
headers,
|
headers,
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
|
addDeviceListener({
|
||||||
|
serialNumber: device.serialNumber,
|
||||||
|
types: ['device_firmware_upgrade'],
|
||||||
|
addToast: (title, body) =>
|
||||||
|
addToast({
|
||||||
|
title,
|
||||||
|
body,
|
||||||
|
color: 'info',
|
||||||
|
autohide: true,
|
||||||
|
}),
|
||||||
|
});
|
||||||
setUpgradeStatus({
|
setUpgradeStatus({
|
||||||
loading: false,
|
loading: false,
|
||||||
result: {
|
result: {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { getItem, setItem } from 'utils/localStorageHelper';
|
|||||||
import DeviceSearchBar from 'components/DeviceSearchBar';
|
import DeviceSearchBar from 'components/DeviceSearchBar';
|
||||||
import DeviceFirmwareModal from 'components/DeviceFirmwareModal';
|
import DeviceFirmwareModal from 'components/DeviceFirmwareModal';
|
||||||
import FirmwareHistoryModal from 'components/FirmwareHistoryModal';
|
import FirmwareHistoryModal from 'components/FirmwareHistoryModal';
|
||||||
|
import { useGlobalWebSocket } from 'contexts/WebSocketProvider';
|
||||||
import { useAuth, useToast } from 'ucentral-libs';
|
import { useAuth, useToast } from 'ucentral-libs';
|
||||||
import Table from './Table';
|
import Table from './Table';
|
||||||
import meshIcon from '../../assets/icons/Mesh.png';
|
import meshIcon from '../../assets/icons/Mesh.png';
|
||||||
@@ -36,6 +37,7 @@ const DeviceList = () => {
|
|||||||
deviceType: '',
|
deviceType: '',
|
||||||
serialNumber: '',
|
serialNumber: '',
|
||||||
});
|
});
|
||||||
|
const { lastMessage } = useGlobalWebSocket();
|
||||||
|
|
||||||
const deviceIcons = {
|
const deviceIcons = {
|
||||||
meshIcon,
|
meshIcon,
|
||||||
@@ -359,6 +361,22 @@ const DeviceList = () => {
|
|||||||
getCount();
|
getCount();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (lastMessage && lastMessage.type === 'DEVICE') {
|
||||||
|
const { serialNumber: msgSerial, isConnected } = lastMessage;
|
||||||
|
if (devices.find(({ serialNumber }) => serialNumber === msgSerial)) {
|
||||||
|
const newDevices = devices.map((device) => {
|
||||||
|
if (device.serialNumber !== msgSerial) return device;
|
||||||
|
return {
|
||||||
|
...device,
|
||||||
|
connected: isConnected,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
setDevices(newDevices);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [lastMessage, devices]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (upgradeStatus.result !== undefined) {
|
if (upgradeStatus.result !== undefined) {
|
||||||
addToast({
|
addToast({
|
||||||
|
|||||||
@@ -133,18 +133,18 @@ const StatisticsChartList = ({ setOptions, section, setStart, setEnd, time }) =>
|
|||||||
if (version > 0) {
|
if (version > 0) {
|
||||||
const prevTx = prevTxObj[inter.name] !== undefined ? prevTxObj[inter.name] : 0;
|
const prevTx = prevTxObj[inter.name] !== undefined ? prevTxObj[inter.name] : 0;
|
||||||
const prevRx = prevTxObj[inter.name] !== undefined ? prevRxObj[inter.name] : 0;
|
const prevRx = prevTxObj[inter.name] !== undefined ? prevRxObj[inter.name] : 0;
|
||||||
const tx = inter.counters ? inter.counters.tx_bytes : 0;
|
const tx = inter.counters ? Math.floor(inter.counters.tx_bytes / 1024) : 0;
|
||||||
const rx = inter.counters ? inter.counters.rx_bytes : 0;
|
const rx = inter.counters ? Math.floor(inter.counters.rx_bytes / 1024) : 0;
|
||||||
interfaceList[interfaceTypes[inter.name]][0].data.push(Math.max(0, tx - prevTx));
|
interfaceList[interfaceTypes[inter.name]][0].data.push(Math.max(0, tx - prevTx));
|
||||||
interfaceList[interfaceTypes[inter.name]][1].data.push(Math.max(0, rx - prevRx));
|
interfaceList[interfaceTypes[inter.name]][1].data.push(Math.max(0, rx - prevRx));
|
||||||
prevTxObj[inter.name] = tx;
|
prevTxObj[inter.name] = tx;
|
||||||
prevRxObj[inter.name] = rx;
|
prevRxObj[inter.name] = rx;
|
||||||
} else {
|
} else {
|
||||||
interfaceList[interfaceTypes[inter.name]][0].data.push(
|
interfaceList[interfaceTypes[inter.name]][0].data.push(
|
||||||
inter.counters ? Math.floor(inter.counters.tx_bytes) : 0,
|
inter.counters ? Math.floor(inter.counters.tx_bytes / 1024) : 0,
|
||||||
);
|
);
|
||||||
interfaceList[interfaceTypes[inter.name]][1].data.push(
|
interfaceList[interfaceTypes[inter.name]][1].data.push(
|
||||||
inter.counters ? Math.floor(inter.counters.rx_bytes) : 0,
|
inter.counters ? Math.floor(inter.counters.rx_bytes / 1024) : 0,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,11 +22,13 @@ import axiosInstance from 'utils/axiosInstance';
|
|||||||
import eventBus from 'utils/eventBus';
|
import eventBus from 'utils/eventBus';
|
||||||
import { LoadingButton, useAuth, useDevice, useToast } from 'ucentral-libs';
|
import { LoadingButton, useAuth, useDevice, useToast } from 'ucentral-libs';
|
||||||
import SuccessfulActionModalBody from 'components/SuccessfulActionModalBody';
|
import SuccessfulActionModalBody from 'components/SuccessfulActionModalBody';
|
||||||
|
import { useGlobalWebSocket } from 'contexts/WebSocketProvider';
|
||||||
|
|
||||||
const ActionModal = ({ show, toggleModal }) => {
|
const ActionModal = ({ show, toggleModal }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { currentToken, endpoints } = useAuth();
|
const { currentToken, endpoints } = useAuth();
|
||||||
const { deviceSerialNumber } = useDevice();
|
const { deviceSerialNumber } = useDevice();
|
||||||
|
const { addDeviceListener } = useGlobalWebSocket();
|
||||||
const { addToast } = useToast();
|
const { addToast } = useToast();
|
||||||
const [waiting, setWaiting] = useState(false);
|
const [waiting, setWaiting] = useState(false);
|
||||||
const [result, setResult] = useState(null);
|
const [result, setResult] = useState(null);
|
||||||
@@ -74,9 +76,20 @@ const ActionModal = ({ show, toggleModal }) => {
|
|||||||
{ headers },
|
{ headers },
|
||||||
)
|
)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
addDeviceListener({
|
||||||
|
serialNumber: deviceSerialNumber,
|
||||||
|
types: ['device_connection', 'device_disconnection'],
|
||||||
|
addToast: (title, body) =>
|
||||||
|
addToast({
|
||||||
|
title,
|
||||||
|
body,
|
||||||
|
color: 'info',
|
||||||
|
autohide: true,
|
||||||
|
}),
|
||||||
|
});
|
||||||
addToast({
|
addToast({
|
||||||
title: t('common.success'),
|
title: t('common.success'),
|
||||||
body: t('commands.command_success'),
|
body: t('commands.reboot_start'),
|
||||||
color: 'success',
|
color: 'success',
|
||||||
autohide: true,
|
autohide: true,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ const TraceModal = ({ show, toggleModal }) => {
|
|||||||
const [responseBody, setResponseBody] = useState('');
|
const [responseBody, setResponseBody] = useState('');
|
||||||
const [chosenInterface, setChosenInterface] = useState('up');
|
const [chosenInterface, setChosenInterface] = useState('up');
|
||||||
const [isDeviceConnected, setIsDeviceConnected] = useState(false);
|
const [isDeviceConnected, setIsDeviceConnected] = useState(false);
|
||||||
const [waitForTrace, setWaitForTrace] = useState(false);
|
const [waitForTrace, setWaitForTrace] = useState(true);
|
||||||
const [waitingForTrace, setWaitingForTrace] = useState(false);
|
const [waitingForTrace, setWaitingForTrace] = useState(false);
|
||||||
const [commandUuid, setCommandUuid] = useState(null);
|
const [commandUuid, setCommandUuid] = useState(null);
|
||||||
|
|
||||||
@@ -49,7 +49,7 @@ const TraceModal = ({ show, toggleModal }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setWaitForTrace(false);
|
setWaitForTrace(true);
|
||||||
setHadSuccess(false);
|
setHadSuccess(false);
|
||||||
setHadFailure(false);
|
setHadFailure(false);
|
||||||
setResponseBody('');
|
setResponseBody('');
|
||||||
@@ -137,25 +137,19 @@ const TraceModal = ({ show, toggleModal }) => {
|
|||||||
<CModalBody>
|
<CModalBody>
|
||||||
<h6>{t('trace.directions')}</h6>
|
<h6>{t('trace.directions')}</h6>
|
||||||
<CRow className="mt-3">
|
<CRow className="mt-3">
|
||||||
<CCol>
|
<CCol md="4" className="pt-2">
|
||||||
<CButton
|
{t('contact.type')}
|
||||||
disabled={blockFields}
|
|
||||||
block
|
|
||||||
color="primary"
|
|
||||||
onClick={() => setUsingDuration(true)}
|
|
||||||
>
|
|
||||||
{t('common.duration')}
|
|
||||||
</CButton>
|
|
||||||
</CCol>
|
</CCol>
|
||||||
<CCol>
|
<CCol xs="12" md="8">
|
||||||
<CButton
|
<CSelect
|
||||||
|
custom
|
||||||
|
value={usingDuration ? 'duration' : 'packets'}
|
||||||
disabled={blockFields}
|
disabled={blockFields}
|
||||||
block
|
onChange={(e) => setUsingDuration(e.target.value === 'duration')}
|
||||||
color="primary"
|
|
||||||
onClick={() => setUsingDuration(false)}
|
|
||||||
>
|
>
|
||||||
{t('trace.packets')}
|
<option value="duration">{t('common.duration')}</option>
|
||||||
</CButton>
|
<option value="packets">{t('trace.packets')}</option>
|
||||||
|
</CSelect>
|
||||||
</CCol>
|
</CCol>
|
||||||
</CRow>
|
</CRow>
|
||||||
<CRow className="mt-3">
|
<CRow className="mt-3">
|
||||||
@@ -220,7 +214,7 @@ const TraceModal = ({ show, toggleModal }) => {
|
|||||||
</CCol>
|
</CCol>
|
||||||
</CRow>
|
</CRow>
|
||||||
<CRow className="mt-3" hidden={!isDeviceConnected}>
|
<CRow className="mt-3" hidden={!isDeviceConnected}>
|
||||||
<CCol md="8">
|
<CCol md="7">
|
||||||
<p>{t('trace.wait_for_file')}</p>
|
<p>{t('trace.wait_for_file')}</p>
|
||||||
</CCol>
|
</CCol>
|
||||||
<CCol>
|
<CCol>
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { useCallback, useMemo } from 'react';
|
||||||
|
import { useToast } from 'ucentral-libs';
|
||||||
|
|
||||||
|
const useWebSocketNotification = () => {
|
||||||
|
const { addToast } = useToast();
|
||||||
|
|
||||||
|
const pushNotification = useCallback((notification) => {
|
||||||
|
addToast({
|
||||||
|
title: notification.content.title,
|
||||||
|
body: notification.content.details,
|
||||||
|
color: 'info',
|
||||||
|
autohide: true,
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const toReturn = useMemo(
|
||||||
|
() => ({
|
||||||
|
pushNotification,
|
||||||
|
}),
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
return toReturn;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useWebSocketNotification;
|
||||||
90
src/contexts/WebSocketProvider/index.js
Normal file
90
src/contexts/WebSocketProvider/index.js
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
import { useAuth } from 'ucentral-libs';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import useWebSocketNotification from './hooks/NotificationContent/useWebSocketNotification';
|
||||||
|
import useSocketReducer from './useSocketReducer';
|
||||||
|
import { extractWebSocketResponse } from './utils';
|
||||||
|
|
||||||
|
const WebSocketContext = React.createContext({
|
||||||
|
webSocket: undefined,
|
||||||
|
isOpen: false,
|
||||||
|
allMessages: [],
|
||||||
|
addDeviceListener: () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const WebSocketProvider = ({ children }) => {
|
||||||
|
const { currentToken, endpoints } = useAuth();
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
const ws = useRef(undefined);
|
||||||
|
const { allMessages, lastMessage, dispatch } = useSocketReducer();
|
||||||
|
const { pushNotification } = useWebSocketNotification();
|
||||||
|
|
||||||
|
const onMessage = useCallback((message) => {
|
||||||
|
const result = extractWebSocketResponse(message);
|
||||||
|
if (result?.type === 'NOTIFICATION') {
|
||||||
|
dispatch({ type: 'NEW_NOTIFICATION', notification: result.notification });
|
||||||
|
pushNotification(result.notification);
|
||||||
|
}
|
||||||
|
if (result?.type === 'DEVICE_NOTIFICATION') {
|
||||||
|
dispatch({
|
||||||
|
type: 'NEW_DEVICE_NOTIFICATION',
|
||||||
|
serialNumber: result.serialNumber,
|
||||||
|
subType: result.subType,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (result?.type === 'COMMAND') {
|
||||||
|
dispatch({ type: 'NEW_COMMAND', data: result.data });
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// useEffect for created the WebSocket and 'storing' it in useRef
|
||||||
|
useEffect(() => {
|
||||||
|
ws.current = new WebSocket(`${endpoints.owgw.replace('https', 'wss')}/api/v1/ws`);
|
||||||
|
ws.current.onopen = () => {
|
||||||
|
setIsOpen(true);
|
||||||
|
ws.current?.send(`token:${currentToken}`);
|
||||||
|
};
|
||||||
|
ws.current.onclose = () => {
|
||||||
|
setIsOpen(false);
|
||||||
|
};
|
||||||
|
ws.current.onerror = () => {
|
||||||
|
setIsOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const wsCurrent = ws?.current;
|
||||||
|
return () => wsCurrent?.close();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// useEffect for generating global notifications
|
||||||
|
useEffect(() => {
|
||||||
|
if (ws?.current) {
|
||||||
|
ws.current.addEventListener('message', onMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
const wsCurrent = ws?.current;
|
||||||
|
return () => {
|
||||||
|
if (wsCurrent) wsCurrent.removeEventListener('message', onMessage);
|
||||||
|
};
|
||||||
|
}, [ws?.current]);
|
||||||
|
const values = useMemo(
|
||||||
|
() => ({
|
||||||
|
allMessages,
|
||||||
|
lastMessage,
|
||||||
|
webSocket: ws.current,
|
||||||
|
addDeviceListener: ({ serialNumber, types, addToast, onTrigger }) =>
|
||||||
|
dispatch({ type: 'ADD_DEVICE_LISTENER', serialNumber, types, addToast, onTrigger }),
|
||||||
|
removeDeviceListener: ({ serialNumber }) =>
|
||||||
|
dispatch({ type: 'REMOVE_DEVICE_LISTENER', serialNumber }),
|
||||||
|
isOpen,
|
||||||
|
}),
|
||||||
|
[ws, isOpen, allMessages, lastMessage],
|
||||||
|
);
|
||||||
|
|
||||||
|
return <WebSocketContext.Provider value={values}>{children}</WebSocketContext.Provider>;
|
||||||
|
};
|
||||||
|
|
||||||
|
WebSocketProvider.propTypes = {
|
||||||
|
children: PropTypes.node.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useGlobalWebSocket = () => React.useContext(WebSocketContext);
|
||||||
114
src/contexts/WebSocketProvider/useSocketReducer.js
Normal file
114
src/contexts/WebSocketProvider/useSocketReducer.js
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
import { useReducer } from 'react';
|
||||||
|
|
||||||
|
const titles = {
|
||||||
|
device_connection: 'Connected',
|
||||||
|
device_disconnection: 'Disconnected',
|
||||||
|
device_firmware_upgrade: 'Firmware Upgraded',
|
||||||
|
};
|
||||||
|
const bodies = {
|
||||||
|
device_connection: 'This device has rebooted and is now connected!',
|
||||||
|
device_disconnection: 'This device has started rebooting and is now disconnected!',
|
||||||
|
device_firmware_upgrade: 'This device has updated to new firmware!',
|
||||||
|
};
|
||||||
|
|
||||||
|
const reducer = (state, action) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case 'NEW_NOTIFICATION': {
|
||||||
|
const obj = { type: 'NOTIFICATION', data: action.notification, timestamp: new Date() };
|
||||||
|
return { allMessages: [...state.allMessages, obj], lastMessage: obj };
|
||||||
|
}
|
||||||
|
case 'NEW_COMMAND': {
|
||||||
|
const obj = {
|
||||||
|
type: 'COMMAND',
|
||||||
|
response: action.data.response,
|
||||||
|
timestamp: new Date(),
|
||||||
|
id: action.data.command_response_id,
|
||||||
|
};
|
||||||
|
return { allMessages: [...state.allMessages, obj], lastMessage: obj };
|
||||||
|
}
|
||||||
|
case 'NEW_DEVICE_NOTIFICATION': {
|
||||||
|
const newListeners = state.deviceListeners;
|
||||||
|
let obj;
|
||||||
|
|
||||||
|
if (action.subType === 'device_connection' || action.subType === 'device_disconnection') {
|
||||||
|
obj = {
|
||||||
|
type: 'DEVICE',
|
||||||
|
isConnected: action.subType === 'device_connection',
|
||||||
|
serialNumber: action.serialNumber,
|
||||||
|
timestamp: new Date(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
for (let i = 0; i < state.deviceListeners.length; i += 1) {
|
||||||
|
if (
|
||||||
|
state.deviceListeners[i].serialNumber === action.serialNumber &&
|
||||||
|
state.deviceListeners[i].type === action.subType
|
||||||
|
) {
|
||||||
|
if (state.deviceListeners[i].onTrigger) {
|
||||||
|
setTimeout(() => state.deviceListeners[i].onTrigger(action.subType), 1000);
|
||||||
|
} else if (state.deviceListeners[i].addToast) {
|
||||||
|
state.deviceListeners[i].addToast(
|
||||||
|
`${action.serialNumber} ${titles[state.deviceListeners[i].type]}`,
|
||||||
|
bodies[state.deviceListeners[i].type],
|
||||||
|
);
|
||||||
|
const found = newListeners.findIndex(
|
||||||
|
(listener) =>
|
||||||
|
listener.serialNumber === action.serialNumber && listener.type === action.subType,
|
||||||
|
);
|
||||||
|
if (found >= 0) newListeners.splice(found, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
allMessages: state.allMessages,
|
||||||
|
lastMessage: obj ?? state.lastMessage,
|
||||||
|
deviceListeners: newListeners,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case 'ADD_DEVICE_LISTENER': {
|
||||||
|
let newListeners = action.types.map((actionType) => ({
|
||||||
|
type: actionType,
|
||||||
|
serialNumber: action.serialNumber,
|
||||||
|
addToast: action.addToast,
|
||||||
|
onTrigger: action.onTrigger,
|
||||||
|
}));
|
||||||
|
newListeners = newListeners.concat(state.deviceListeners);
|
||||||
|
return {
|
||||||
|
allMessages: state.allMessages,
|
||||||
|
lastMessage: state.lastMessage,
|
||||||
|
deviceListeners: newListeners,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case 'REMOVE_DEVICE_LISTENER': {
|
||||||
|
const newListeners = state.deviceListeners.filter(
|
||||||
|
(listener) =>
|
||||||
|
listener.serialNumber !== action.serialNumber || listener.onTrigger === undefined,
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
allMessages: state.allMessages,
|
||||||
|
lastMessage: state.lastMessage,
|
||||||
|
deviceListeners: newListeners,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case 'UNKNOWN': {
|
||||||
|
const obj = { type: 'UNKNOWN', data: action.newMessage, timestamp: new Date() };
|
||||||
|
return {
|
||||||
|
allMessages: [...state.allMessages, obj],
|
||||||
|
lastMessage: obj,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const useSocketReducer = () => {
|
||||||
|
const [{ allMessages, lastMessage, deviceListeners }, dispatch] = useReducer(reducer, {
|
||||||
|
allMessages: [],
|
||||||
|
deviceListeners: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
return { allMessages, lastMessage, deviceListeners, dispatch };
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useSocketReducer;
|
||||||
54
src/contexts/WebSocketProvider/utils.js
Normal file
54
src/contexts/WebSocketProvider/utils.js
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
export const acceptedNotificationTypes = [
|
||||||
|
'venue_configuration_update',
|
||||||
|
'entity_configuration_update',
|
||||||
|
];
|
||||||
|
export const deviceNotificationTypes = [
|
||||||
|
'device_connection',
|
||||||
|
'device_disconnection',
|
||||||
|
'device_firmware_upgrade',
|
||||||
|
];
|
||||||
|
|
||||||
|
export const extractWebSocketResponse = (message) => {
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(message.data);
|
||||||
|
if (data.notification && acceptedNotificationTypes.includes(data.notification.type)) {
|
||||||
|
const { notification } = data;
|
||||||
|
return { notification, type: 'NOTIFICATION' };
|
||||||
|
}
|
||||||
|
if (data.notification && deviceNotificationTypes.includes(data.notification.type)) {
|
||||||
|
return {
|
||||||
|
serialNumber: data.notification.content.serialNumber,
|
||||||
|
type: 'DEVICE_NOTIFICATION',
|
||||||
|
subType: data.notification.type,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (data.command_response_id) {
|
||||||
|
return { data, type: 'COMMAND' };
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getStatusFromNotification = (notification) => {
|
||||||
|
let status = 'success';
|
||||||
|
if (notification.content.warning?.length > 0) status = 'warning';
|
||||||
|
if (notification.content.error?.length > 0) status = 'error';
|
||||||
|
|
||||||
|
return status;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getNotificationDescription = (t, notification) => {
|
||||||
|
if (
|
||||||
|
notification.content.type === 'venue_configuration_update' ||
|
||||||
|
notification.content.type === 'entity_configuration_update'
|
||||||
|
) {
|
||||||
|
return t('configurations.notification_details', {
|
||||||
|
success: notification.content.success.length,
|
||||||
|
warning: notification.content.warning.length,
|
||||||
|
error: notification.content.error.length,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return notification.content.details;
|
||||||
|
};
|
||||||
@@ -5,6 +5,7 @@ import { CSidebarNavItem } from '@coreui/react';
|
|||||||
import { cilBarcode, cilRouter, cilSave, cilSettings, cilPeople } from '@coreui/icons';
|
import { cilBarcode, cilRouter, cilSave, cilSettings, cilPeople } from '@coreui/icons';
|
||||||
import CIcon from '@coreui/icons-react';
|
import CIcon from '@coreui/icons-react';
|
||||||
import { Header, Sidebar, Footer, PageContainer, ToastProvider, useAuth } from 'ucentral-libs';
|
import { Header, Sidebar, Footer, PageContainer, ToastProvider, useAuth } from 'ucentral-libs';
|
||||||
|
import { WebSocketProvider } from 'contexts/WebSocketProvider';
|
||||||
|
|
||||||
const TheLayout = () => {
|
const TheLayout = () => {
|
||||||
const [showSidebar, setShowSidebar] = useState('responsive');
|
const [showSidebar, setShowSidebar] = useState('responsive');
|
||||||
@@ -70,7 +71,9 @@ const TheLayout = () => {
|
|||||||
/>
|
/>
|
||||||
<div className="c-body">
|
<div className="c-body">
|
||||||
<ToastProvider>
|
<ToastProvider>
|
||||||
<PageContainer t={t} routes={routes} redirectTo="/devices" />
|
<WebSocketProvider>
|
||||||
|
<PageContainer t={t} routes={routes} redirectTo="/devices" />
|
||||||
|
</WebSocketProvider>
|
||||||
</ToastProvider>
|
</ToastProvider>
|
||||||
</div>
|
</div>
|
||||||
<Footer t={t} version={process.env.VERSION} />
|
<Footer t={t} version={process.env.VERSION} />
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import ConfigurationDisplay from 'components/ConfigurationDisplay';
|
import ConfigurationDisplay from 'components/ConfigurationDisplay';
|
||||||
import WifiAnalysis from 'components/WifiAnalysis';
|
import WifiAnalysis from 'components/WifiAnalysis';
|
||||||
import CapabilitiesDisplay from 'components/CapabilitiesDisplay';
|
import CapabilitiesDisplay from 'components/CapabilitiesDisplay';
|
||||||
|
import { useGlobalWebSocket } from 'contexts/WebSocketProvider';
|
||||||
import NotesTab from './NotesTab';
|
import NotesTab from './NotesTab';
|
||||||
import DeviceDetails from './Details';
|
import DeviceDetails from './Details';
|
||||||
import DeviceStatusCard from './DeviceStatusCard';
|
import DeviceStatusCard from './DeviceStatusCard';
|
||||||
@@ -26,6 +27,7 @@ const DevicePage = () => {
|
|||||||
const [deviceConfig, setDeviceConfig] = useState(null);
|
const [deviceConfig, setDeviceConfig] = useState(null);
|
||||||
const [error, setError] = useState(false);
|
const [error, setError] = useState(false);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
const { addDeviceListener, removeDeviceListener } = useGlobalWebSocket();
|
||||||
|
|
||||||
const updateNav = (target) => {
|
const updateNav = (target) => {
|
||||||
sessionStorage.setItem('devicePageIndex', target);
|
sessionStorage.setItem('devicePageIndex', target);
|
||||||
@@ -115,9 +117,22 @@ const DevicePage = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setError(false);
|
setError(false);
|
||||||
if (deviceId) {
|
if (deviceId) {
|
||||||
|
addDeviceListener({
|
||||||
|
serialNumber: deviceId,
|
||||||
|
types: ['device_connection', 'device_disconnection', 'device_firmware_upgrade'],
|
||||||
|
onTrigger: () => getData(),
|
||||||
|
});
|
||||||
getDevice();
|
getDevice();
|
||||||
getData();
|
getData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (deviceId) {
|
||||||
|
removeDeviceListener({
|
||||||
|
serialNumber: deviceId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
}, [deviceId]);
|
}, [deviceId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ axiosRetry(axiosInstance, {
|
|||||||
retryDelay: () => axiosRetry.exponentialDelay,
|
retryDelay: () => axiosRetry.exponentialDelay,
|
||||||
});
|
});
|
||||||
|
|
||||||
axiosInstance.defaults.timeout = 60000;
|
axiosInstance.defaults.timeout = 160000;
|
||||||
axiosInstance.defaults.headers.get.Accept = 'application/json';
|
axiosInstance.defaults.headers.get.Accept = 'application/json';
|
||||||
axiosInstance.defaults.headers.post.Accept = 'application/json';
|
axiosInstance.defaults.headers.post.Accept = 'application/json';
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user