Compare commits

...

11 Commits

Author SHA1 Message Date
TIP Automation User
4b79a0b74c Chg: update image tag in helm values to v2.7.0 2022-10-05 11:32:56 +00:00
TIP Automation User
c158f0aef8 Chg: update image tag in helm values to v2.7.0-RC2 2022-09-29 23:27:39 +00:00
jaspreetsachdev
4e5c6a9426 Merge pull request #112 from Telecominfraproject/main
Fixes for WIFI-10904 and others
2022-09-29 19:15:21 -04:00
jaspreetsachdev
7ad184cb48 Merge branch 'release/v2.7.0' into main 2022-09-29 19:15:03 -04:00
Charles Bourque
41a7d5d0a8 Merge pull request #111 from stephb9959/main
[WIFI-10904] Websocket more resilient in case of disconnection
2022-09-23 12:42:28 +01:00
Charles
78c48e004c [WIFI-10904] Websocket more resilient in case of disconnection
Signed-off-by: Charles <charles.bourque96@gmail.com>
2022-09-23 12:41:00 +01:00
Charles Bourque
7106d61881 Merge pull request #110 from stephb9959/main
[WIFI-10904] Connection statistics on the sidebar
2022-09-22 19:55:28 +01:00
Charles
8ead4c4708 [WIFI-10904] Connection statistics on the sidebar
Signed-off-by: Charles <charles.bourque96@gmail.com>
2022-09-22 19:54:21 +01:00
Charles Bourque
52ca7d3503 Merge pull request #109 from stephb9959/main
[WIFI-10894] Status column added to command history
2022-09-21 13:55:11 +01:00
Charles Bourque
7d504da0a8 Merge pull request #108 from stephb9959/main
[WIFI-10894] Status column added to command history
2022-09-21 13:54:10 +01:00
Charles
c6dee2252b [WIFI-10894] Status column added to command history
Signed-off-by: Charles <charles.bourque96@gmail.com>
2022-09-21 13:53:24 +01:00
11 changed files with 244 additions and 24 deletions

View File

@@ -8,7 +8,7 @@ fullnameOverride: ""
images:
owgwui:
repository: tip-tip-wlan-cloud-ucentral.jfrog.io/owgw-ui
tag: v2.7.0-RC1
tag: v2.7.0
pullPolicy: Always
services:

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "ucentral-client",
"version": "2.7.0(6)",
"version": "2.7.0(8)",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "ucentral-client",
"version": "2.7.0(6)",
"version": "2.7.0(8)",
"dependencies": {
"@coreui/coreui": "^3.4.0",
"@coreui/icons": "^2.0.1",

View File

@@ -1,6 +1,6 @@
{
"name": "ucentral-client",
"version": "2.7.0(6)",
"version": "2.7.0(8)",
"dependencies": {
"@coreui/coreui": "^3.4.0",
"@coreui/icons": "^2.0.1",

View File

@@ -205,10 +205,11 @@ const DeviceCommands = () => {
const columns = [
{ key: 'submitted', label: t('common.submitted'), filter: false, _style: { width: '20%' } },
{ key: 'command', label: t('common.command'), _style: { width: '15%' } },
{ key: 'command', label: t('common.command'), _style: { width: '0%' } },
{ key: 'status', label: t('common.status'), _style: { width: '0%' } },
{ key: 'executed', label: t('common.executed'), filter: false, _style: { width: '16%' } },
{ key: 'completed', label: t('common.completed'), filter: false, _style: { width: '16%' } },
{ key: 'errorCode', label: t('common.error_code'), filter: false, _style: { width: '8%' } },
{ key: 'errorCode', label: t('common.error_code'), filter: false },
{
key: 'show_buttons',
label: '',
@@ -317,16 +318,17 @@ const DeviceCommands = () => {
{item.completed && item.completed !== 0 ? (
<FormattedDate date={item.completed} />
) : (
'Pending'
'-'
)}
</td>
),
status: (item) => <td className="align-middle">{item.status}</td>,
executed: (item) => (
<td className="align-middle">
{item.executed && item.executed !== 0 ? (
<FormattedDate date={item.executed} />
) : (
'Pending'
'-'
)}
</td>
),
@@ -335,7 +337,7 @@ const DeviceCommands = () => {
{item.submitted && item.submitted !== '' ? (
<FormattedDate date={item.submitted} />
) : (
'Pending'
'-'
)}
</td>
),

View File

@@ -11,7 +11,7 @@ const WebSocketContext = React.createContext({
addDeviceListener: () => {},
});
export const WebSocketProvider = ({ children }) => {
export const WebSocketProvider = ({ children, setNewConnectionData }) => {
const { currentToken, endpoints } = useAuth();
const [isOpen, setIsOpen] = useState(false);
const ws = useRef(undefined);
@@ -20,6 +20,9 @@ export const WebSocketProvider = ({ children }) => {
const onMessage = useCallback((message) => {
const result = extractWebSocketResponse(message);
if (result?.type === 'device_connections_statistics') {
setNewConnectionData(result.content);
}
if (result?.type === 'NOTIFICATION') {
dispatch({ type: 'NEW_NOTIFICATION', notification: result.notification });
pushNotification(result.notification);
@@ -36,24 +39,29 @@ export const WebSocketProvider = ({ children }) => {
}
}, []);
// useEffect for created the WebSocket and 'storing' it in useRef
useEffect(() => {
if (endpoints?.owgw !== undefined) {
ws.current = new WebSocket(`${endpoints.owgw.replace('https', 'wss')}/api/v1/ws`);
const onStartWebSocket = () => {
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);
setTimeout(onStartWebSocket, 3000);
};
ws.current.onerror = () => {
setIsOpen(false);
};
};
// useEffect for created the WebSocket and 'storing' it in useRef
useEffect(() => {
if (endpoints?.owgw !== undefined) {
onStartWebSocket();
}
const wsCurrent = ws?.current;
return () => wsCurrent?.close();
}, []);
}, [endpoints]);
// useEffect for generating global notifications
useEffect(() => {
@@ -66,6 +74,7 @@ export const WebSocketProvider = ({ children }) => {
if (wsCurrent) wsCurrent.removeEventListener('message', onMessage);
};
}, [ws?.current]);
const values = useMemo(
() => ({
lastMessage,
@@ -84,6 +93,7 @@ export const WebSocketProvider = ({ children }) => {
WebSocketProvider.propTypes = {
children: PropTypes.node.isRequired,
setNewConnectionData: PropTypes.func.isRequired,
};
export const useGlobalWebSocket = () => React.useContext(WebSocketContext);

View File

@@ -26,6 +26,9 @@ export const extractWebSocketResponse = (message) => {
if (data.command_response_id) {
return { data, type: 'COMMAND' };
}
if (data.notification.type === 'device_connections_statistics') {
return { content: data.notification.content, type: 'device_connections_statistics' };
}
} catch {
return undefined;
}

120
src/layout/Devices.js Normal file
View File

@@ -0,0 +1,120 @@
import React, { useState, useEffect } from 'react';
import axiosInstance from 'utils/axiosInstance';
import { useAuth } from 'ucentral-libs';
import { CPopover } from '@coreui/react';
import { extraCompactSecondsToDetailed, secondsToDetailed } from 'utils/helper';
import { useTranslation } from 'react-i18next';
import PropTypes from 'prop-types';
const propTypes = {
newData: PropTypes.instanceOf(Object),
};
const defaultProps = {
newData: undefined,
};
const SidebarDevices = ({ newData }) => {
const { t } = useTranslation();
const { currentToken, endpoints } = useAuth();
const [stats, setStats] = useState();
const [lastUpdate, setLastUpdate] = useState();
const [lastTime, setLastTime] = useState();
const getInitialStats = async () => {
const options = {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${currentToken}`,
},
};
axiosInstance
.get(`${endpoints.owgw}/api/v1/devices?connectionStatistics=true`, options)
.then(({ data }) => {
setStats(data);
setLastUpdate(new Date());
})
.catch(() => {});
};
const getTime = () => {
if (lastTime === undefined || lastUpdate === undefined) return null;
const seconds = lastTime.getTime() - lastUpdate.getTime();
return Math.max(0, Math.floor(seconds / 1000));
};
useEffect(() => {
if (newData !== undefined && Object.keys(newData).length > 0) {
setStats({ ...newData });
setLastUpdate(new Date());
}
}, [newData]);
useEffect(() => {
getInitialStats();
}, []);
useEffect(() => {
const interval = setInterval(() => {
setLastTime(new Date());
}, 1000);
return () => {
clearInterval(interval);
};
}, []);
if (!stats) {
return null;
}
return (
<div
style={{
position: 'absolute',
bottom: '0px',
width: '100%',
background: '#2f3d54 !important',
backgroundColor: '#2f3d54 !important',
borderTop: '3px solid #d8dbe0',
color: 'white',
textAlign: 'center',
paddingTop: '15px',
paddingBottom: '25px',
}}
>
<h3 style={{ marginBottom: '0px' }}>{stats?.connectedDevices ?? stats?.numberOfDevices}</h3>
<h6>Connected Devices</h6>
<CPopover
content={secondsToDetailed(
stats?.averageConnectionTime ?? stats?.averageConnectedTime,
t('common.day'),
t('common.days'),
t('common.hour'),
t('common.hours'),
t('common.minute'),
t('common.minutes'),
t('common.second'),
t('common.seconds'),
)}
>
<h3 style={{ marginBottom: '0px' }}>
{extraCompactSecondsToDetailed(
stats?.averageConnectionTime ?? stats?.averageConnectedTime,
t('common.day'),
t('common.days'),
t('common.seconds'),
)}
</h3>
</CPopover>
<h6>Avg. Connection Time</h6>
<h7 style={{ color: '#ebedef', fontStyle: 'italic' }}>{getTime()} seconds ago</h7>
</div>
);
};
SidebarDevices.propTypes = propTypes;
SidebarDevices.defaultProps = defaultProps;
export default React.memo(SidebarDevices);

View File

@@ -0,0 +1,49 @@
import React from 'react';
import { CSidebar, CSidebarBrand, CSidebarNav } from '@coreui/react';
import PropTypes from 'prop-types';
import styles from './index.module.scss';
const Sidebar = ({
showSidebar,
setShowSidebar,
logo,
options,
redirectTo,
logoHeight,
logoWidth,
}) => (
<CSidebar show={showSidebar} onShowChange={(val) => setShowSidebar(val)}>
<CSidebarBrand className="d-md-down-none" to={redirectTo}>
<img
className={[styles.sidebarImgFull, 'c-sidebar-brand-full'].join(' ')}
style={{ height: logoHeight ?? undefined, width: logoWidth ?? undefined }}
src={logo}
alt="OpenWifi"
/>
<img
className={[styles.sidebarImgMinimized, 'c-sidebar-brand-minimized'].join(' ')}
style={{ height: logoHeight ?? undefined, width: logoWidth ?? undefined }}
src={logo}
alt="OpenWifi"
/>
</CSidebarBrand>
<CSidebarNav>{options}</CSidebarNav>
</CSidebar>
);
Sidebar.propTypes = {
showSidebar: PropTypes.string.isRequired,
setShowSidebar: PropTypes.func.isRequired,
logo: PropTypes.string.isRequired,
options: PropTypes.node.isRequired,
redirectTo: PropTypes.string.isRequired,
logoHeight: PropTypes.string,
logoWidth: PropTypes.string,
};
Sidebar.defaultProps = {
logoHeight: null,
logoWidth: null,
};
export default React.memo(Sidebar);

View File

@@ -0,0 +1,9 @@
.sidebarImgFull {
height: 75px;
width: 175px;
}
.sidebarImgMinimized {
height: 75px;
width: 75px;
}

View File

@@ -4,13 +4,20 @@ import routes from 'routes';
import { CSidebarNavItem } from '@coreui/react';
import { cilBarcode, cilRouter, cilSave, cilSettings, cilPeople } from '@coreui/icons';
import CIcon from '@coreui/icons-react';
import { Header, Sidebar, Footer, PageContainer, ToastProvider, useAuth } from 'ucentral-libs';
import { Header, Footer, PageContainer, ToastProvider, useAuth } from 'ucentral-libs';
import { WebSocketProvider } from 'contexts/WebSocketProvider';
import Sidebar from './Sidebar';
import SidebarDevices from './Devices';
const TheLayout = () => {
const [showSidebar, setShowSidebar] = useState('responsive');
const { endpoints, currentToken, user, avatar, logout } = useAuth();
const { t, i18n } = useTranslation();
const [newConnectionData, setNewConnectionData] = useState();
const onConnectionDataChange = React.useCallback((newData) => {
setNewConnectionData({ ...newData });
}, []);
return (
<div className="c-app c-default-layout">
@@ -50,6 +57,7 @@ const TheLayout = () => {
to="/system"
icon={<CIcon content={cilSettings} size="xl" className="mr-3" />}
/>
<SidebarDevices newData={newConnectionData} />
</>
}
redirectTo="/devices"
@@ -71,7 +79,7 @@ const TheLayout = () => {
/>
<div className="c-body">
<ToastProvider>
<WebSocketProvider>
<WebSocketProvider setNewConnectionData={onConnectionDataChange}>
<PageContainer t={t} routes={routes} redirectTo="/devices" />
</WebSocketProvider>
</ToastProvider>

View File

@@ -130,6 +130,25 @@ export const compactSecondsToDetailed = (seconds, dayLabel, daysLabel, secondsLa
return finalString;
};
export const extraCompactSecondsToDetailed = (seconds) => {
let secondsLeft = seconds;
const days = Math.floor(secondsLeft / (3600 * 24));
secondsLeft -= days * (3600 * 24);
const hours = Math.floor(secondsLeft / 3600);
secondsLeft -= hours * 3600;
const minutes = Math.floor(secondsLeft / 60);
secondsLeft -= minutes * 60;
let finalString = '';
finalString = `${finalString}${prettyNumber(days)}:`;
finalString = `${finalString}${prettyNumber(hours)}:`;
finalString = `${finalString}${prettyNumber(minutes)}:`;
finalString = `${finalString}${prettyNumber(secondsLeft)}`;
return finalString;
};
export const validateEmail = (email) => {
const regex = /\S+@\S+\.\S+/;
return regex.test(email);