mirror of
https://github.com/Telecominfraproject/wlan-cloud-ucentral-ui-libs.git
synced 2025-10-30 02:12:22 +00:00
Dashboards, firmware history, device list fixes
This commit is contained in:
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "ucentral-libs",
|
||||
"version": "0.8.34",
|
||||
"version": "0.8.43",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "ucentral-libs",
|
||||
"version": "0.8.34",
|
||||
"version": "0.8.43",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.14.6",
|
||||
"@babel/plugin-proposal-class-properties": "^7.14.5",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ucentral-libs",
|
||||
"version": "0.8.34",
|
||||
"version": "0.8.43",
|
||||
"main": "dist/index.js",
|
||||
"source": "src/index.js",
|
||||
"engines": {
|
||||
|
||||
68
src/components/DeviceBadge/index.js
Normal file
68
src/components/DeviceBadge/index.js
Normal file
@@ -0,0 +1,68 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { CPopover } from '@coreui/react';
|
||||
import styles from './index.module.scss';
|
||||
|
||||
const DeviceBadge = ({ t, device, deviceIcons }) => {
|
||||
const [src, setSrc] = useState('');
|
||||
|
||||
const getSrc = () => {
|
||||
switch (device.deviceType) {
|
||||
case 'AP':
|
||||
setSrc(deviceIcons.apIcon);
|
||||
break;
|
||||
case 'MESH':
|
||||
setSrc(deviceIcons.meshIcon);
|
||||
break;
|
||||
case 'SWITCH':
|
||||
setSrc(deviceIcons.internetSwitch);
|
||||
break;
|
||||
case 'IOT':
|
||||
setSrc(deviceIcons.iotIcon);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const getCertColor = () => {
|
||||
switch (device.verifiedCertificate) {
|
||||
case 'VALID_CERTIFICATE':
|
||||
case 'NO_CERTIFICATE':
|
||||
return 'bg-danger';
|
||||
case 'MISMATCH_SERIAL':
|
||||
return 'bg-warning';
|
||||
case 'VERIFIED':
|
||||
return 'bg-success';
|
||||
default:
|
||||
return 'bg-danger';
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getSrc();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<CPopover content={device.verifiedCertificate} placement="top">
|
||||
<div className={`c-avatar c-avatar-lg ${getCertColor()}`}>
|
||||
<img src={src} className={styles.icon} alt={device.deviceType} />
|
||||
<CPopover content={device.connected ? t('common.connected') : t('common.not_connected')}>
|
||||
<span
|
||||
className={
|
||||
device.connected ? 'c-avatar-status bg-success' : 'c-avatar-status bg-danger'
|
||||
}
|
||||
/>
|
||||
</CPopover>
|
||||
</div>
|
||||
</CPopover>
|
||||
);
|
||||
};
|
||||
|
||||
DeviceBadge.propTypes = {
|
||||
t: PropTypes.func.isRequired,
|
||||
device: PropTypes.instanceOf(Object).isRequired,
|
||||
deviceIcons: PropTypes.instanceOf(Object).isRequired,
|
||||
};
|
||||
|
||||
export default React.memo(DeviceBadge);
|
||||
4
src/components/DeviceBadge/index.module.scss
Normal file
4
src/components/DeviceBadge/index.module.scss
Normal file
@@ -0,0 +1,4 @@
|
||||
.icon {
|
||||
height: 36px;
|
||||
width: 36px;
|
||||
}
|
||||
@@ -1,16 +1,80 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { CCard, CCardBody, CCardHeader, CCol, CRow } from '@coreui/react';
|
||||
import { CCard, CCardBody, CCardHeader, CCol, CRow, CWidgetIcon } from '@coreui/react';
|
||||
import { CChartBar, CChartPie } from '@coreui/react-chartjs';
|
||||
import { cilClock, cilMedicalCross, cilThumbUp, cilWarning } from '@coreui/icons';
|
||||
import CIcon from '@coreui/icons-react';
|
||||
import { prettyDate } from '../../utils/formatting';
|
||||
|
||||
const getColor = (health) => {
|
||||
const numberHealth = health ? Number(health.replace('%', '')) : 0;
|
||||
if (numberHealth >= 90) return 'success';
|
||||
if (numberHealth >= 60) return 'warning';
|
||||
return 'danger';
|
||||
};
|
||||
|
||||
const getIcon = (health) => {
|
||||
const numberHealth = health ? Number(health.replace('%', '')) : 0;
|
||||
if (numberHealth >= 90) return <CIcon width={36} name="cil-thumbs-up" content={cilThumbUp} />;
|
||||
if (numberHealth >= 60) return <CIcon width={36} name="cil-warning" content={cilWarning} />;
|
||||
return <CIcon width={36} name="cil-medical-cross" content={cilMedicalCross} />;
|
||||
};
|
||||
|
||||
const DeviceDashboard = ({ t, data }) => (
|
||||
<div>
|
||||
<CRow>
|
||||
<CCol>
|
||||
<CWidgetIcon
|
||||
text={t('common.last_dashboard_refresh')}
|
||||
header={<h2>{data.snapshot ? prettyDate(data.snapshot) : ''}</h2>}
|
||||
color="info"
|
||||
iconPadding={false}
|
||||
>
|
||||
<CIcon width={36} name="cil-clock" content={cilClock} />
|
||||
</CWidgetIcon>
|
||||
</CCol>
|
||||
<CCol>
|
||||
<CWidgetIcon
|
||||
text={t('common.overall_health')}
|
||||
header={<h2>{data.overallHealth}</h2>}
|
||||
color={getColor(data.overallHealth)}
|
||||
iconPadding={false}
|
||||
>
|
||||
{getIcon(data.overallHealth)}
|
||||
</CWidgetIcon>
|
||||
</CCol>
|
||||
<CCol>
|
||||
<CWidgetIcon
|
||||
text={t('common.devices')}
|
||||
header={<h2>{data.numberOfDevices}</h2>}
|
||||
color="primary"
|
||||
iconPadding={false}
|
||||
>
|
||||
<CIcon width={36} name="cil-router" />
|
||||
</CWidgetIcon>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow>
|
||||
<CCol>
|
||||
<CCard>
|
||||
<CCardHeader>{t('common.device_status')}</CCardHeader>
|
||||
<CCardBody>
|
||||
<CChartPie datasets={data.status.datasets} labels={data.status.labels} />
|
||||
<CChartPie
|
||||
datasets={data.status.datasets}
|
||||
labels={data.status.labels}
|
||||
options={{
|
||||
tooltips: {
|
||||
callbacks: {
|
||||
title: (item, ds) => ds.labels[item[0].index],
|
||||
label: (item, ds) => `${ds.datasets[0].data[item.index]}%`,
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
display: true,
|
||||
position: 'right',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</CCardBody>
|
||||
</CCard>
|
||||
</CCol>
|
||||
@@ -18,15 +82,49 @@ const DeviceDashboard = ({ t, data }) => (
|
||||
<CCard>
|
||||
<CCardHeader>{t('common.device_health')}</CCardHeader>
|
||||
<CCardBody>
|
||||
<CChartPie datasets={data.healths.datasets} labels={data.healths.labels} />
|
||||
<CChartPie
|
||||
datasets={data.healths.datasets}
|
||||
labels={data.healths.labels}
|
||||
options={{
|
||||
tooltips: {
|
||||
callbacks: {
|
||||
title: (item, ds) => ds.labels[item[0].index],
|
||||
label: (item, ds) =>
|
||||
`${ds.datasets[0].data[item.index]}${t('common.of_connected')}`,
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
display: true,
|
||||
position: 'right',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</CCardBody>
|
||||
</CCard>
|
||||
</CCol>
|
||||
<CCol>
|
||||
<CCard>
|
||||
<CCardHeader>{t('common.uptimes')}</CCardHeader>
|
||||
<CCardHeader>{t('wifi_analysis.associations')}</CCardHeader>
|
||||
<CCardBody>
|
||||
<CChartBar datasets={data.upTimes.datasets} labels={data.upTimes.labels} />
|
||||
<CChartPie
|
||||
datasets={data.associations.datasets}
|
||||
labels={data.associations.labels}
|
||||
options={{
|
||||
tooltips: {
|
||||
callbacks: {
|
||||
title: (item, ds) => ds.labels[item[0].index],
|
||||
label: (item, ds) =>
|
||||
`${ds.datasets[0].data[item.index]}% of ${
|
||||
data.totalAssociations
|
||||
} associations`,
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
display: true,
|
||||
position: 'right',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</CCardBody>
|
||||
</CCard>
|
||||
</CCol>
|
||||
@@ -36,7 +134,24 @@ const DeviceDashboard = ({ t, data }) => (
|
||||
<CCard>
|
||||
<CCardHeader>{t('common.vendors')}</CCardHeader>
|
||||
<CCardBody>
|
||||
<CChartBar datasets={data.vendors.datasets} labels={data.vendors.labels} />
|
||||
<CChartBar
|
||||
datasets={data.vendors.datasets}
|
||||
labels={data.vendors.labels}
|
||||
options={{
|
||||
tooltips: {
|
||||
mode: 'index',
|
||||
intersect: false,
|
||||
},
|
||||
hover: {
|
||||
mode: 'index',
|
||||
intersect: false,
|
||||
},
|
||||
legend: {
|
||||
display: false,
|
||||
position: 'right',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</CCardBody>
|
||||
</CCard>
|
||||
</CCol>
|
||||
@@ -44,7 +159,48 @@ const DeviceDashboard = ({ t, data }) => (
|
||||
<CCard>
|
||||
<CCardHeader>{t('firmware.device_types')}</CCardHeader>
|
||||
<CCardBody>
|
||||
<CChartPie datasets={data.deviceType.datasets} labels={data.deviceType.labels} />
|
||||
<CChartPie
|
||||
datasets={data.deviceType.datasets}
|
||||
labels={data.deviceType.labels}
|
||||
options={{
|
||||
tooltips: {
|
||||
callbacks: {
|
||||
title: (item, ds) => ds.labels[item[0].index],
|
||||
label: (item, ds) =>
|
||||
`${ds.datasets[0].data[item.index]} ${t('common.devices')}`,
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
display: true,
|
||||
position: 'right',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</CCardBody>
|
||||
</CCard>
|
||||
</CCol>
|
||||
<CCol>
|
||||
<CCard>
|
||||
<CCardHeader>{t('common.uptimes')}</CCardHeader>
|
||||
<CCardBody>
|
||||
<CChartBar
|
||||
datasets={data.upTimes.datasets}
|
||||
labels={data.upTimes.labels}
|
||||
options={{
|
||||
tooltips: {
|
||||
mode: 'index',
|
||||
intersect: false,
|
||||
},
|
||||
hover: {
|
||||
mode: 'index',
|
||||
intersect: false,
|
||||
},
|
||||
legend: {
|
||||
display: false,
|
||||
position: 'right',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</CCardBody>
|
||||
</CCard>
|
||||
</CCol>
|
||||
@@ -54,7 +210,22 @@ const DeviceDashboard = ({ t, data }) => (
|
||||
<CCard>
|
||||
<CCardHeader>{t('common.certificates')}</CCardHeader>
|
||||
<CCardBody>
|
||||
<CChartPie datasets={data.certificates.datasets} labels={data.certificates.labels} />
|
||||
<CChartPie
|
||||
datasets={data.certificates.datasets}
|
||||
labels={data.certificates.labels}
|
||||
options={{
|
||||
tooltips: {
|
||||
callbacks: {
|
||||
title: (item, ds) => ds.labels[item[0].index],
|
||||
label: (item, ds) => `${ds.datasets[0].data[item.index]}%`,
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
display: true,
|
||||
position: 'right',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</CCardBody>
|
||||
</CCard>
|
||||
</CCol>
|
||||
@@ -62,7 +233,24 @@ const DeviceDashboard = ({ t, data }) => (
|
||||
<CCard>
|
||||
<CCardHeader>{t('common.commands')}</CCardHeader>
|
||||
<CCardBody>
|
||||
<CChartBar datasets={data.commands.datasets} labels={data.commands.labels} />
|
||||
<CChartBar
|
||||
datasets={data.commands.datasets}
|
||||
labels={data.commands.labels}
|
||||
options={{
|
||||
tooltips: {
|
||||
mode: 'index',
|
||||
intersect: false,
|
||||
},
|
||||
hover: {
|
||||
mode: 'index',
|
||||
intersect: false,
|
||||
},
|
||||
legend: {
|
||||
display: false,
|
||||
position: 'right',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</CCardBody>
|
||||
</CCard>
|
||||
</CCol>
|
||||
@@ -70,7 +258,24 @@ const DeviceDashboard = ({ t, data }) => (
|
||||
<CCard>
|
||||
<CCardHeader>{t('common.memory_used')}</CCardHeader>
|
||||
<CCardBody>
|
||||
<CChartBar datasets={data.memoryUsed.datasets} labels={data.memoryUsed.labels} />
|
||||
<CChartBar
|
||||
datasets={data.memoryUsed.datasets}
|
||||
labels={data.memoryUsed.labels}
|
||||
options={{
|
||||
tooltips: {
|
||||
mode: 'index',
|
||||
intersect: false,
|
||||
},
|
||||
hover: {
|
||||
mode: 'index',
|
||||
intersect: false,
|
||||
},
|
||||
legend: {
|
||||
display: false,
|
||||
position: 'right',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</CCardBody>
|
||||
</CCard>
|
||||
</CCol>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
CButton,
|
||||
CDataTable,
|
||||
CModal,
|
||||
CModalHeader,
|
||||
@@ -9,6 +10,7 @@ import {
|
||||
CRow,
|
||||
CCol,
|
||||
CInput,
|
||||
CModalFooter,
|
||||
} from '@coreui/react';
|
||||
import { cleanBytesString, prettyDate } from '../../utils/formatting';
|
||||
import LoadingButton from '../LoadingButton';
|
||||
@@ -38,7 +40,7 @@ const DeviceFirmwareModal = ({
|
||||
|
||||
return (
|
||||
<CModal show={show} onClose={toggle} size="xl">
|
||||
<CModalHeader>
|
||||
<CModalHeader closeButton>
|
||||
<CModalTitle>#{device?.serialNumber}</CModalTitle>
|
||||
</CModalHeader>
|
||||
<CModalBody>
|
||||
@@ -96,6 +98,11 @@ const DeviceFirmwareModal = ({
|
||||
<div />
|
||||
)}
|
||||
</CModalBody>
|
||||
<CModalFooter>
|
||||
<CButton color="secondary" onClick={toggle}>
|
||||
{t('common.close')}
|
||||
</CButton>
|
||||
</CModalFooter>
|
||||
</CModal>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,7 +2,6 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ReactPaginate from 'react-paginate';
|
||||
import {
|
||||
CBadge,
|
||||
CCardBody,
|
||||
CDataTable,
|
||||
CButton,
|
||||
@@ -17,14 +16,25 @@ import {
|
||||
CDropdownToggle,
|
||||
CDropdownMenu,
|
||||
CDropdownDivider,
|
||||
CDropdownItem,
|
||||
CButtonGroup,
|
||||
} from '@coreui/react';
|
||||
import { cilSync, cilInfo, cilBadge, cilBan, cilNotes, cilSave } from '@coreui/icons';
|
||||
import {
|
||||
cilSync,
|
||||
cilNotes,
|
||||
cilArrowCircleTop,
|
||||
cilCheckCircle,
|
||||
cilWifiSignal2,
|
||||
cilTerminal,
|
||||
cilTrash,
|
||||
} from '@coreui/icons';
|
||||
import CIcon from '@coreui/icons-react';
|
||||
import styles from './index.module.scss';
|
||||
import { cleanBytesString } from '../../utils/formatting';
|
||||
import DeviceBadge from '../DeviceBadge';
|
||||
import LoadingButton from '../LoadingButton';
|
||||
|
||||
const DeviceListTable = ({
|
||||
currentPage,
|
||||
devices,
|
||||
devicesPerPage,
|
||||
loading,
|
||||
@@ -34,118 +44,37 @@ const DeviceListTable = ({
|
||||
refreshDevice,
|
||||
t,
|
||||
toggleFirmwareModal,
|
||||
toggleHistoryModal,
|
||||
upgradeToLatest,
|
||||
upgradeStatus,
|
||||
meshIcon,
|
||||
apIcon,
|
||||
internetSwitch,
|
||||
iotIcon,
|
||||
deviceIcons,
|
||||
connectRtty,
|
||||
deleteDevice,
|
||||
deleteStatus,
|
||||
}) => {
|
||||
const columns = [
|
||||
{ key: 'deviceType', label: '', filter: false, sorter: false, _style: { width: '3%' } },
|
||||
{ key: 'verifiedCertificate', label: t('common.certificate'), _style: { width: '1%' } },
|
||||
{ key: 'serialNumber', label: t('common.serial_number'), _style: { width: '6%' } },
|
||||
{ key: 'UUID', label: t('common.config_id'), _style: { width: '6%' } },
|
||||
{ key: 'firmware', label: t('firmware.revision'), filter: false, _style: { width: '28%' } },
|
||||
{ key: 'firmware', label: t('firmware.revision') },
|
||||
{ key: 'firmware_button', label: '', filter: false, _style: { width: '4%' } },
|
||||
{ key: 'compatible', label: t('firmware.device_type'), filter: false, _style: { width: '6%' } },
|
||||
{ key: 'txBytes', label: 'Tx', filter: false, _style: { width: '11%' } },
|
||||
{ key: 'rxBytes', label: 'Rx', filter: false, _style: { width: '11%' } },
|
||||
{ key: 'ipAddress', label: t('common.ip_address'), _style: { width: '8%' } },
|
||||
{ key: 'wifi_analysis', label: t(''), _style: { width: '4%' } },
|
||||
{ key: 'show_details', label: t(''), _style: { width: '4%' } },
|
||||
{ key: 'refresh_device', label: t(''), _style: { width: '4%' } },
|
||||
{ key: 'compatible', label: t('common.type'), filter: false, _style: { width: '6%' } },
|
||||
{ key: 'txBytes', label: 'Tx', filter: false, _style: { width: '8%' } },
|
||||
{ key: 'rxBytes', label: 'Rx', filter: false, _style: { width: '8%' } },
|
||||
{ key: 'ipAddress', label: t('IP'), _style: { width: '8%' } },
|
||||
{ key: 'actions', label: '', _style: { width: '1%' } },
|
||||
];
|
||||
|
||||
const getDeviceIcon = (deviceType) => {
|
||||
if (deviceType === 'AP_Default' || deviceType === 'AP') {
|
||||
return <img src={apIcon} className={styles.icon} alt="AP" />;
|
||||
}
|
||||
if (deviceType === 'MESH') {
|
||||
return <img src={meshIcon} className={styles.icon} alt="MESH" />;
|
||||
}
|
||||
if (deviceType === 'SWITCH') {
|
||||
return <img src={internetSwitch} className={styles.icon} alt="SWITCH" />;
|
||||
}
|
||||
if (deviceType === 'IOT') {
|
||||
return <img src={iotIcon} className={styles.icon} alt="SWITCH" />;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const getCertBadge = (cert) => {
|
||||
if (cert === 'NO_CERTIFICATE') {
|
||||
return (
|
||||
<div className={styles.certificateWrapper}>
|
||||
<CIcon className={styles.badge} name="cil-badge" content={cilBadge} size="2xl" alt="AP" />
|
||||
<CIcon
|
||||
className={styles.badCertificate}
|
||||
name="cil-ban"
|
||||
content={cilBan}
|
||||
size="3xl"
|
||||
alt="AP"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
let color = 'transparent';
|
||||
switch (cert) {
|
||||
case 'VALID_CERTIFICATE':
|
||||
color = 'danger';
|
||||
break;
|
||||
case 'MISMATCH_SERIAL':
|
||||
return (
|
||||
<CBadge color={color} className={styles.mismatchBackground}>
|
||||
<CIcon name="cil-badge" content={cilBadge} size="2xl" alt="AP" />
|
||||
</CBadge>
|
||||
);
|
||||
case 'VERIFIED':
|
||||
color = 'success';
|
||||
break;
|
||||
default:
|
||||
return (
|
||||
<div className={styles.certificateWrapper}>
|
||||
<CIcon
|
||||
className={styles.badge}
|
||||
name="cil-badge"
|
||||
content={cilBadge}
|
||||
size="2xl"
|
||||
alt="AP"
|
||||
/>
|
||||
<CIcon
|
||||
className={styles.badCertificate}
|
||||
name="cil-ban"
|
||||
content={cilBan}
|
||||
size="3xl"
|
||||
alt="AP"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<CBadge color={color}>
|
||||
<CIcon name="cil-badge" content={cilBadge} size="2xl" alt="AP" />
|
||||
</CBadge>
|
||||
);
|
||||
};
|
||||
|
||||
const getStatusBadge = (status) => {
|
||||
if (status) {
|
||||
return 'success';
|
||||
}
|
||||
return 'danger';
|
||||
};
|
||||
|
||||
const getFirmwareButton = (latest, device) => {
|
||||
let text = t('firmware.unknown_firmware_status');
|
||||
let upgradeText = t('firmware.upgrade_to_latest');
|
||||
let icon = <CIcon size="lg" name="cil-arrow-circle-top" content={cilArrowCircleTop} />;
|
||||
let color = 'secondary';
|
||||
if (latest !== undefined) {
|
||||
text = t('firmware.newer_firmware_available');
|
||||
color = 'warning';
|
||||
|
||||
if (latest) {
|
||||
icon = <CIcon size="lg" name="cil-check-circle" content={cilCheckCircle} />;
|
||||
text = t('firmware.latest_version_installed');
|
||||
upgradeText = t('firmware.reinstall_latest');
|
||||
color = 'success';
|
||||
@@ -154,14 +83,14 @@ const DeviceListTable = ({
|
||||
return (
|
||||
<CDropdown>
|
||||
<CDropdownToggle caret={false} color={color}>
|
||||
<CIcon size="sm" content={cilSave} />
|
||||
{icon}
|
||||
</CDropdownToggle>
|
||||
<CDropdownMenu style={{ width: '250px' }} className="mt-2 mb-2 mx-5" placement="bottom">
|
||||
<CRow className="pl-3">
|
||||
<CRow color="secondary" className="pl-3">
|
||||
<CCol>{text}</CCol>
|
||||
</CRow>
|
||||
<CDropdownDivider />
|
||||
<CRow className="pl-3 mt-1">
|
||||
<CRow className="pl-3 mt-3">
|
||||
<CCol>
|
||||
<LoadingButton
|
||||
label={upgradeText}
|
||||
@@ -185,11 +114,55 @@ const DeviceListTable = ({
|
||||
</CButton>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className="pl-3 mt-3">
|
||||
<CCol>
|
||||
<CButton
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
toggleHistoryModal(device);
|
||||
}}
|
||||
>
|
||||
{t('firmware.history_title')}
|
||||
</CButton>
|
||||
</CCol>
|
||||
</CRow>
|
||||
</CDropdownMenu>
|
||||
</CDropdown>
|
||||
);
|
||||
};
|
||||
|
||||
const deleteButton = (serialNumber) => (
|
||||
<CPopover content={t('common.delete_device')}>
|
||||
<CDropdown>
|
||||
<CDropdownToggle className="btn-outline-primary btn-sm btn-square" caret={false}>
|
||||
<CIcon name="cil-trash" content={cilTrash} size="sm" />
|
||||
</CDropdownToggle>
|
||||
<CDropdownMenu
|
||||
style={{ width: '250px' }}
|
||||
className="mt-2 mb-2 mx-5"
|
||||
placement="bottom-start"
|
||||
>
|
||||
<CRow className="pl-3">
|
||||
<CCol>{t('common.device_delete', { serialNumber })}</CCol>
|
||||
</CRow>
|
||||
<CDropdownDivider />
|
||||
<CDropdownItem>
|
||||
<LoadingButton
|
||||
data-toggle="dropdown"
|
||||
color="danger"
|
||||
label={t('common.confirm')}
|
||||
isLoadingLabel={t('user.deleting')}
|
||||
isLoading={deleteStatus.loading}
|
||||
action={() => deleteDevice(serialNumber)}
|
||||
block
|
||||
disabled={deleteStatus.loading}
|
||||
/>
|
||||
</CDropdownItem>
|
||||
</CDropdownMenu>
|
||||
</CDropdown>
|
||||
</CPopover>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<CCard>
|
||||
@@ -219,8 +192,13 @@ const DeviceListTable = ({
|
||||
border
|
||||
loading={loading}
|
||||
scopedSlots={{
|
||||
deviceType: (item) => (
|
||||
<td className="pt-3 text-center">
|
||||
<DeviceBadge t={t} device={item} deviceIcons={deviceIcons} />
|
||||
</td>
|
||||
),
|
||||
serialNumber: (item) => (
|
||||
<td className="text-center">
|
||||
<td className="text-center align-middle">
|
||||
<CLink
|
||||
className="c-subheader-nav-link"
|
||||
aria-current="page"
|
||||
@@ -230,105 +208,88 @@ const DeviceListTable = ({
|
||||
</CLink>
|
||||
</td>
|
||||
),
|
||||
deviceType: (item) => (
|
||||
<td className="pt-3 text-center">
|
||||
<CPopover
|
||||
content={item.connected ? t('common.connected') : t('common.not_connected')}
|
||||
placement="top"
|
||||
>
|
||||
<CBadge size="sm" color={getStatusBadge(item.connected)}>
|
||||
{getDeviceIcon(item.deviceType) ?? item.deviceType}
|
||||
</CBadge>
|
||||
</CPopover>
|
||||
</td>
|
||||
),
|
||||
verifiedCertificate: (item) => (
|
||||
<td className="text-center">
|
||||
<CPopover
|
||||
content={item.verifiedCertificate ?? t('common.unknown')}
|
||||
placement="top"
|
||||
>
|
||||
{getCertBadge(item.verifiedCertificate)}
|
||||
</CPopover>
|
||||
</td>
|
||||
),
|
||||
firmware: (item) => (
|
||||
<td>
|
||||
<td className="align-middle">
|
||||
<CPopover
|
||||
content={item.firmware ? item.firmware : t('common.na')}
|
||||
placement="top"
|
||||
>
|
||||
<p style={{ width: 'calc(20vw)' }} className="text-truncate">
|
||||
<div style={{ width: 'calc(22vw)' }} className="text-truncate align-middle">
|
||||
{item.firmware}
|
||||
</p>
|
||||
</div>
|
||||
</CPopover>
|
||||
</td>
|
||||
),
|
||||
firmware_button: (item) => (
|
||||
<td className="text-center">
|
||||
<td className="text-center align-middle">
|
||||
{item.firmwareInfo
|
||||
? getFirmwareButton(item.firmwareInfo.latest, item)
|
||||
: getFirmwareButton(undefined, item)}
|
||||
</td>
|
||||
),
|
||||
compatible: (item) => (
|
||||
<td>
|
||||
<td className="align-middle">
|
||||
<CPopover
|
||||
content={item.compatible ? item.compatible : t('common.na')}
|
||||
placement="top"
|
||||
>
|
||||
<p style={{ width: 'calc(6vw)' }} className="text-truncate">
|
||||
<div style={{ width: 'calc(8vw)' }} className="text-truncate align-middle">
|
||||
{item.compatible}
|
||||
</p>
|
||||
</div>
|
||||
</CPopover>
|
||||
</td>
|
||||
),
|
||||
txBytes: (item) => <td>{cleanBytesString(item.txBytes)}</td>,
|
||||
rxBytes: (item) => <td>{cleanBytesString(item.rxBytes)}</td>,
|
||||
txBytes: (item) => <td className="align-middle">{cleanBytesString(item.txBytes)}</td>,
|
||||
rxBytes: (item) => <td className="align-middle">{cleanBytesString(item.rxBytes)}</td>,
|
||||
ipAddress: (item) => (
|
||||
<td>
|
||||
<td className="align-middle">
|
||||
<CPopover
|
||||
content={item.ipAddress ? item.ipAddress : t('common.na')}
|
||||
placement="top"
|
||||
>
|
||||
<p style={{ width: 'calc(8vw)' }} className="text-truncate">
|
||||
<div style={{ width: 'calc(7vw)' }} className="text-truncate align-middle">
|
||||
{item.ipAddress}
|
||||
</p>
|
||||
</div>
|
||||
</CPopover>
|
||||
</td>
|
||||
),
|
||||
wifi_analysis: (item) => (
|
||||
<td className="text-center">
|
||||
<CPopover content={t('configuration.details')}>
|
||||
<CLink
|
||||
className="c-subheader-nav-link"
|
||||
aria-current="page"
|
||||
to={() => `/devices/${item.serialNumber}`}
|
||||
>
|
||||
<CButton color="primary" variant="outline" shape="square" size="sm">
|
||||
<CIcon name="cil-info" content={cilInfo} size="sm" />
|
||||
</CButton>
|
||||
</CLink>
|
||||
</CPopover>
|
||||
</td>
|
||||
),
|
||||
show_details: (item) => (
|
||||
<td className="text-center">
|
||||
actions: (item) => (
|
||||
<td className="text-center align-middle">
|
||||
<CButtonGroup role="group">
|
||||
<CPopover content={t('wifi_analysis.title')}>
|
||||
<CLink
|
||||
className="c-subheader-nav-link"
|
||||
aria-current="page"
|
||||
to={() => `/devices/${item.serialNumber}/wifianalysis`}
|
||||
>
|
||||
<CButton color="primary" variant="outline" shape="square" size="sm">
|
||||
<CIcon name="cil-wifi-signal-2" content={cilWifiSignal2} size="sm" />
|
||||
</CButton>
|
||||
</CLink>
|
||||
</CPopover>
|
||||
<CPopover content={t('actions.connect')}>
|
||||
<CButton
|
||||
color="primary"
|
||||
variant="outline"
|
||||
shape="square"
|
||||
size="sm"
|
||||
onClick={() => connectRtty(item.serialNumber)}
|
||||
>
|
||||
<CIcon name="cil-terminal" content={cilTerminal} size="sm" />
|
||||
</CButton>
|
||||
</CPopover>
|
||||
{deleteButton(item.serialNumber)}
|
||||
<CPopover content={t('configuration.details')}>
|
||||
<CLink
|
||||
className="c-subheader-nav-link"
|
||||
aria-current="page"
|
||||
to={() => `/devices/${item.serialNumber}`}
|
||||
>
|
||||
<CButton color="primary" variant="outline" shape="square" size="sm">
|
||||
<CIcon name="cil-notes" content={cilNotes} size="sm" />
|
||||
</CButton>
|
||||
</CLink>
|
||||
</CPopover>
|
||||
</td>
|
||||
),
|
||||
refresh_device: (item) => (
|
||||
<td className="text-center">
|
||||
<CPopover content={t('common.refresh_device')}>
|
||||
<CButton
|
||||
onClick={() => refreshDevice(item.serialNumber)}
|
||||
@@ -339,6 +300,7 @@ const DeviceListTable = ({
|
||||
<CIcon name="cil-sync" content={cilSync} size="sm" />
|
||||
</CButton>
|
||||
</CPopover>
|
||||
</CButtonGroup>
|
||||
</td>
|
||||
),
|
||||
}}
|
||||
@@ -348,6 +310,7 @@ const DeviceListTable = ({
|
||||
nextLabel="Next →"
|
||||
pageCount={pageCount}
|
||||
onPageChange={updatePage}
|
||||
forcePage={Number(currentPage)}
|
||||
breakClassName="page-item"
|
||||
breakLinkClassName="page-link"
|
||||
containerClassName="pagination"
|
||||
@@ -366,6 +329,7 @@ const DeviceListTable = ({
|
||||
};
|
||||
|
||||
DeviceListTable.propTypes = {
|
||||
currentPage: PropTypes.string,
|
||||
devices: PropTypes.instanceOf(Array).isRequired,
|
||||
updateDevicesPerPage: PropTypes.func.isRequired,
|
||||
pageCount: PropTypes.number.isRequired,
|
||||
@@ -375,12 +339,17 @@ DeviceListTable.propTypes = {
|
||||
t: PropTypes.func.isRequired,
|
||||
loading: PropTypes.bool.isRequired,
|
||||
toggleFirmwareModal: PropTypes.func.isRequired,
|
||||
toggleHistoryModal: PropTypes.func.isRequired,
|
||||
upgradeToLatest: PropTypes.func.isRequired,
|
||||
upgradeStatus: PropTypes.instanceOf(Object).isRequired,
|
||||
meshIcon: PropTypes.string.isRequired,
|
||||
apIcon: PropTypes.string.isRequired,
|
||||
internetSwitch: PropTypes.string.isRequired,
|
||||
iotIcon: PropTypes.string.isRequired,
|
||||
deviceIcons: PropTypes.instanceOf(Object).isRequired,
|
||||
connectRtty: PropTypes.func.isRequired,
|
||||
deleteDevice: PropTypes.func.isRequired,
|
||||
deleteStatus: PropTypes.instanceOf(Object).isRequired,
|
||||
};
|
||||
|
||||
DeviceListTable.defaultProps = {
|
||||
currentPage: '0',
|
||||
};
|
||||
|
||||
export default React.memo(DeviceListTable);
|
||||
|
||||
228
src/components/FirmwareDashboard/index.js
Normal file
228
src/components/FirmwareDashboard/index.js
Normal file
@@ -0,0 +1,228 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { CCard, CCardBody, CCardHeader, CCol, CDataTable, CRow, CWidgetIcon } from '@coreui/react';
|
||||
import { CChartBar, CChartPie } from '@coreui/react-chartjs';
|
||||
import { cilClock, cilHappy, cilMeh, cilFrown } from '@coreui/icons';
|
||||
import CIcon from '@coreui/icons-react';
|
||||
import { prettyDate } from '../../utils/formatting';
|
||||
|
||||
const getLatestColor = (percent = 0) => {
|
||||
const numberPercent = percent ? Number(percent.replace('%', '')) : 0;
|
||||
if (numberPercent >= 90) return 'success';
|
||||
if (numberPercent > 60) return 'warning';
|
||||
return 'danger';
|
||||
};
|
||||
|
||||
const getLatestIcon = (percent = 0) => {
|
||||
const numberPercent = percent ? Number(percent.replace('%', '')) : 0;
|
||||
if (numberPercent >= 90) return <CIcon width={36} name="cil-happy" content={cilHappy} />;
|
||||
if (numberPercent > 60) return <CIcon width={36} name="cil-meh" content={cilMeh} />;
|
||||
return <CIcon width={36} name="cil-frown" content={cilFrown} />;
|
||||
};
|
||||
|
||||
const FirmwareDashboard = ({ t, data }) => {
|
||||
const columns = [
|
||||
{ key: 'endpoint', label: t('common.endpoint'), filter: false, sorter: false },
|
||||
{ key: 'devices', label: t('common.devices') },
|
||||
{ key: 'percent', label: '' },
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<CRow>
|
||||
<CCol>
|
||||
<CWidgetIcon
|
||||
text={t('common.last_dashboard_refresh')}
|
||||
header={<h2>{data.snapshot ? prettyDate(data.snapshot) : ''}</h2>}
|
||||
color="info"
|
||||
iconPadding={false}
|
||||
>
|
||||
<CIcon width={36} name="cil-clock" content={cilClock} />
|
||||
</CWidgetIcon>
|
||||
</CCol>
|
||||
<CCol>
|
||||
<CWidgetIcon
|
||||
text={t('common.up_to_date')}
|
||||
header={<h2>{data.latestSoftwareRate}</h2>}
|
||||
color={getLatestColor(data.latestSoftwareRate)}
|
||||
iconPadding={false}
|
||||
>
|
||||
{getLatestIcon(data.latestSoftwareRate)}
|
||||
</CWidgetIcon>
|
||||
</CCol>
|
||||
<CCol>
|
||||
<CWidgetIcon
|
||||
text={t('common.devices')}
|
||||
header={<h2>{data.numberOfDevices}</h2>}
|
||||
color="primary"
|
||||
iconPadding={false}
|
||||
>
|
||||
<CIcon width={36} name="cil-router" />
|
||||
</CWidgetIcon>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow>
|
||||
<CCol>
|
||||
<CCard>
|
||||
<CCardHeader>{t('common.firmware_installed')}</CCardHeader>
|
||||
<CCardBody>
|
||||
<CChartPie
|
||||
datasets={data.firmwareDistribution.datasets}
|
||||
labels={data.firmwareDistribution.labels}
|
||||
options={{
|
||||
legend: {
|
||||
display: true,
|
||||
position: 'right',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</CCardBody>
|
||||
</CCard>
|
||||
</CCol>
|
||||
<CCol>
|
||||
<CCard>
|
||||
<CCardHeader>{t('common.devices_using_latest')}</CCardHeader>
|
||||
<CCardBody>
|
||||
<CChartBar
|
||||
datasets={data.latest.datasets}
|
||||
labels={data.latest.labels}
|
||||
options={{
|
||||
tooltips: {
|
||||
mode: 'index',
|
||||
intersect: false,
|
||||
},
|
||||
hover: {
|
||||
mode: 'index',
|
||||
intersect: false,
|
||||
},
|
||||
legend: {
|
||||
display: false,
|
||||
position: 'right',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</CCardBody>
|
||||
</CCard>
|
||||
</CCol>
|
||||
<CCol>
|
||||
<CCard>
|
||||
<CCardHeader>Unknown Firmware</CCardHeader>
|
||||
<CCardBody>
|
||||
<CChartBar
|
||||
datasets={data.unknownFirmwares.datasets}
|
||||
labels={data.unknownFirmwares.labels}
|
||||
options={{
|
||||
tooltips: {
|
||||
mode: 'index',
|
||||
intersect: false,
|
||||
},
|
||||
hover: {
|
||||
mode: 'index',
|
||||
intersect: false,
|
||||
},
|
||||
legend: {
|
||||
display: false,
|
||||
position: 'right',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</CCardBody>
|
||||
</CCard>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow>
|
||||
<CCol>
|
||||
<CCard>
|
||||
<CCardHeader>{t('common.device_status')}</CCardHeader>
|
||||
<CCardBody>
|
||||
<CChartPie
|
||||
datasets={data.status.datasets}
|
||||
labels={data.status.labels}
|
||||
options={{
|
||||
tooltips: {
|
||||
callbacks: {
|
||||
title: (item, ds) => ds.labels[item[0].index],
|
||||
label: (item, ds) => `${ds.datasets[0].data[item.index]}%`,
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
display: true,
|
||||
position: 'right',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</CCardBody>
|
||||
</CCard>
|
||||
</CCol>
|
||||
<CCol>
|
||||
<CCard>
|
||||
<CCardHeader>{t('firmware.device_types')}</CCardHeader>
|
||||
<CCardBody>
|
||||
<CChartPie
|
||||
datasets={data.deviceType.datasets}
|
||||
labels={data.deviceType.labels}
|
||||
options={{
|
||||
tooltips: {
|
||||
callbacks: {
|
||||
title: (item, ds) => ds.labels[item[0].index],
|
||||
label: (item, ds) =>
|
||||
`${ds.datasets[0].data[item.index]} ${t('common.devices')}`,
|
||||
},
|
||||
},
|
||||
legend: {
|
||||
display: true,
|
||||
position: 'right',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</CCardBody>
|
||||
</CCard>
|
||||
</CCol>
|
||||
<CCol>
|
||||
<CCard>
|
||||
<CCardHeader>OUIs</CCardHeader>
|
||||
<CCardBody>
|
||||
<CChartBar
|
||||
datasets={data.ouis.datasets}
|
||||
labels={data.ouis.labels}
|
||||
options={{
|
||||
tooltips: {
|
||||
mode: 'index',
|
||||
intersect: false,
|
||||
},
|
||||
hover: {
|
||||
mode: 'index',
|
||||
intersect: false,
|
||||
},
|
||||
legend: {
|
||||
display: false,
|
||||
position: 'right',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</CCardBody>
|
||||
</CCard>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow>
|
||||
<CCol>
|
||||
<CCard>
|
||||
<CCardHeader>{t('common.endpoints')}</CCardHeader>
|
||||
<CCardBody>
|
||||
<CDataTable items={data.endpoints ?? []} fields={columns} hover border />
|
||||
</CCardBody>
|
||||
</CCard>
|
||||
</CCol>
|
||||
<CCol />
|
||||
<CCol />
|
||||
</CRow>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
FirmwareDashboard.propTypes = {
|
||||
t: PropTypes.func.isRequired,
|
||||
data: PropTypes.instanceOf(Object).isRequired,
|
||||
};
|
||||
|
||||
export default React.memo(FirmwareDashboard);
|
||||
36
src/components/FirmwareHistoryTable/index.js
Normal file
36
src/components/FirmwareHistoryTable/index.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { CDataTable } from '@coreui/react';
|
||||
import { prettyDate } from '../../utils/formatting';
|
||||
|
||||
const FirmwareHistoryModal = ({ t, loading, data }) => {
|
||||
const columns = [
|
||||
{ key: 'date', label: '#', _style: { width: '20%' } },
|
||||
{ key: 'fromRelease', label: t('firmware.from_release'), sorter: false },
|
||||
{ key: 'toRelease', label: t('firmware.to_release'), sorter: false },
|
||||
];
|
||||
|
||||
return (
|
||||
<CDataTable
|
||||
addTableClasses="ignore-overflow"
|
||||
fields={columns}
|
||||
items={data}
|
||||
hover
|
||||
border
|
||||
loading={loading}
|
||||
sorter
|
||||
sorterValue={{ column: 'radio', asc: true }}
|
||||
scopedSlots={{
|
||||
date: (item) => <td>{prettyDate(item.upgraded)}</td>,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
FirmwareHistoryModal.propTypes = {
|
||||
t: PropTypes.func.isRequired,
|
||||
loading: PropTypes.bool.isRequired,
|
||||
data: PropTypes.instanceOf(Array).isRequired,
|
||||
};
|
||||
|
||||
export default React.memo(FirmwareHistoryModal);
|
||||
@@ -4,7 +4,15 @@ import { CDataTable, CRow, CCol, CLabel, CInput } from '@coreui/react';
|
||||
import { prettyDate } from '../../utils/formatting';
|
||||
import LoadingButton from '../LoadingButton';
|
||||
|
||||
const NotesTable = ({ t, notes, addNote, loading, size, extraFunctionParameter }) => {
|
||||
const NotesTable = ({
|
||||
t,
|
||||
notes,
|
||||
addNote,
|
||||
loading,
|
||||
size,
|
||||
extraFunctionParameter,
|
||||
descriptionColumn,
|
||||
}) => {
|
||||
const [currentNote, setCurrentNote] = useState('');
|
||||
|
||||
const columns = [
|
||||
@@ -21,6 +29,57 @@ const NotesTable = ({ t, notes, addNote, loading, size, extraFunctionParameter }
|
||||
setCurrentNote('');
|
||||
}, [notes]);
|
||||
|
||||
if (!descriptionColumn) {
|
||||
return (
|
||||
<div>
|
||||
<CRow>
|
||||
<CCol>
|
||||
<CInput
|
||||
id="notes-input"
|
||||
name="text-input"
|
||||
value={currentNote}
|
||||
onChange={(e) => setCurrentNote(e.target.value)}
|
||||
/>
|
||||
</CCol>
|
||||
<CCol sm={size === 'm' ? '3' : '2'}>
|
||||
<LoadingButton
|
||||
label={t('common.add')}
|
||||
isLoadingLabel={t('common.adding_ellipsis')}
|
||||
isLoading={loading}
|
||||
action={saveNote}
|
||||
disabled={loading || currentNote === ''}
|
||||
/>
|
||||
</CCol>
|
||||
</CRow>
|
||||
<CRow className="pt-3">
|
||||
<CCol>
|
||||
<div className="overflow-auto" style={{ height: '200px' }}>
|
||||
<CDataTable
|
||||
striped
|
||||
responsive
|
||||
border
|
||||
loading={loading}
|
||||
fields={columns}
|
||||
items={notes || []}
|
||||
noItemsView={{ noItems: t('common.no_items') }}
|
||||
sorterValue={{ column: 'created', desc: 'true' }}
|
||||
scopedSlots={{
|
||||
created: (item) => (
|
||||
<td>
|
||||
{item.created && item.created !== 0
|
||||
? prettyDate(item.created)
|
||||
: t('common.na')}
|
||||
</td>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</CCol>
|
||||
</CRow>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<CRow>
|
||||
@@ -80,11 +139,13 @@ NotesTable.propTypes = {
|
||||
loading: PropTypes.bool.isRequired,
|
||||
size: PropTypes.string,
|
||||
extraFunctionParameter: PropTypes.string,
|
||||
descriptionColumn: PropTypes.bool,
|
||||
};
|
||||
|
||||
NotesTable.defaultProps = {
|
||||
size: 'm',
|
||||
extraFunctionParameter: '',
|
||||
descriptionColumn: true,
|
||||
};
|
||||
|
||||
export default NotesTable;
|
||||
|
||||
@@ -4,7 +4,7 @@ import { CDataTable } from '@coreui/react';
|
||||
|
||||
const RadioAnalysisTable = ({ data, loading }) => {
|
||||
const columns = [
|
||||
{ key: 'radio', label: 'R', _style: { width: '5%' } },
|
||||
{ key: 'radio', label: '#', _style: { width: '5%' } },
|
||||
{ key: 'channel', label: 'Ch', _style: { width: '5%' } },
|
||||
{ key: 'channelWidth', label: 'C Width', _style: { width: '7%' }, sorter: false },
|
||||
{ key: 'noise', label: 'Noise', _style: { width: '4%' }, sorter: false },
|
||||
|
||||
@@ -4,18 +4,18 @@ import { CButton, CDataTable, CPopover } from '@coreui/react';
|
||||
|
||||
const WifiAnalysisTable = ({ t, data, loading }) => {
|
||||
const columns = [
|
||||
{ key: 'radio', label: 'R', _style: { width: '5%' } },
|
||||
{ key: 'radio', label: '#', _style: { width: '5%' } },
|
||||
{ key: 'bssid', label: 'BSSID', _style: { width: '14%' } },
|
||||
{ key: 'mode', label: t('wifi_analysis.mode'), _style: { width: '9%' }, sorter: false },
|
||||
{ key: 'ssid', label: 'SSID', _style: { width: '17%' } },
|
||||
{ key: 'rssi', label: 'RSSI', _style: { width: '5%' }, sorter: false },
|
||||
{ key: 'rxRate', label: 'Rx Rate', _style: { width: '7%' }, sorter: false },
|
||||
{ key: 'rxBytes', label: 'Rx Bytes', _style: { width: '7%' }, sorter: false },
|
||||
{ key: 'rxBytes', label: 'Rx', _style: { width: '7%' }, sorter: false },
|
||||
{ key: 'rxMcs', label: 'Rx MCS', _style: { width: '6%' }, sorter: false },
|
||||
{ key: 'rxNss', label: 'Rx NSS', _style: { width: '6%' }, sorter: false },
|
||||
{ key: 'txRate', label: 'Tx Rate', _style: { width: '7%' }, sorter: false },
|
||||
{ key: 'txBytes', label: 'Tx Bytes', _style: { width: '7%' }, sorter: false },
|
||||
{ key: 'ips', label: 'Ip Addr.', _style: { width: '6%' }, sorter: false },
|
||||
{ key: 'txBytes', label: 'Tx', _style: { width: '7%' }, sorter: false },
|
||||
{ key: 'ips', label: 'IP', _style: { width: '6%' }, sorter: false },
|
||||
];
|
||||
|
||||
const centerIfEmpty = (value) => (
|
||||
|
||||
@@ -16,14 +16,17 @@ export { default as EditMyProfile } from './components/EditMyProfile';
|
||||
export { default as Avatar } from './components/Avatar';
|
||||
export { default as WifiAnalysisTable } from './components/WifiAnalysisTable';
|
||||
export { default as RadioAnalysisTable } from './components/RadioAnalysisTable';
|
||||
export { default as FirmwareHistoryTable } from './components/FirmwareHistoryTable';
|
||||
export { default as ApiStatusCard } from './components/ApiStatusCard';
|
||||
export { default as FirmwareList } from './components/FirmwareList';
|
||||
export { default as DeviceFirmwareModal } from './components/DeviceFirmwareModal';
|
||||
export { default as DeviceListTable } from './components/DeviceListTable';
|
||||
export { default as NotesTable } from './components/NotesTable';
|
||||
|
||||
// Pages
|
||||
export { default as LoginPage } from './components/LoginPage';
|
||||
export { default as DeviceDashboard } from './components/DeviceDashboard';
|
||||
export { default as FirmwareDashboard } from './components/FirmwareDashboard';
|
||||
|
||||
// Hooks
|
||||
export { default as useFormFields } from './hooks/useFormFields';
|
||||
|
||||
Reference in New Issue
Block a user