General UI fixes, Settings page, WifiAnalysisTable

This commit is contained in:
bourquecharles
2021-07-23 16:58:00 -04:00
parent e13131e07f
commit 1806f5fb99
10 changed files with 405 additions and 77 deletions

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "ucentral-libs",
"version": "0.8.19",
"version": "0.8.22",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "ucentral-libs",
"version": "0.8.19",
"version": "0.8.22",
"devDependencies": {
"@babel/core": "^7.14.6",
"@babel/plugin-proposal-class-properties": "^7.14.5",

View File

@@ -1,6 +1,6 @@
{
"name": "ucentral-libs",
"version": "0.8.19",
"version": "0.8.22",
"main": "dist/index.js",
"source": "src/index.js",
"engines": {

View File

@@ -0,0 +1,32 @@
import React from 'react';
import PropTypes from 'prop-types';
import { CCard, CCardBody, CCardHeader, CRow, CCol } from '@coreui/react';
const ApiStatusCard = ({ t, info }) => (
<CCard>
<CCardHeader>{info.title}</CCardHeader>
<CCardBody>
<CRow>
<CCol sm="4">
<div block="true">{t('common.endpoint')}:</div>
<div block="true">{t('common.start')}:</div>
<div block="true">{t('status.uptime')}:</div>
<div block="true">{t('footer.version')}:</div>
</CCol>
<CCol>
<div block="true">{info.endpoint}</div>
<div block="true">{info.start}</div>
<div block="true">{info.uptime}</div>
<div block="true">{info.version}</div>
</CCol>
</CRow>
</CCardBody>
</CCard>
);
ApiStatusCard.propTypes = {
t: PropTypes.func.isRequired,
info: PropTypes.instanceOf(Object).isRequired,
};
export default React.memo(ApiStatusCard);

View File

@@ -118,7 +118,7 @@ const EditMyProfile = ({
</CLabel>
<CCol sm="4">
<CRow>
<CCol sm="2" className="pt-2">
<CCol sm="3" className="pt-2">
{t('common.current')}
<div className="pt-5">Preview</div>
</CCol>
@@ -128,7 +128,7 @@ const EditMyProfile = ({
<Avatar src={newAvatar} fallback={user.email.value} />
</div>
</CCol>
<CCol sm="4" className="pt-2">
<CCol className="pt-2">
<div className="mt-1 mb-4">
<LoadingButton
label={t('user.delete_avatar')}
@@ -165,7 +165,7 @@ const EditMyProfile = ({
</CFormGroup>
<CRow>
<CCol />
<CCol xs={1} className="mt-2 text-right">
<CCol xs={2} className="mt-2 text-right">
<CLink
className="c-subheader-nav-link"
aria-current="page"

View File

@@ -0,0 +1,41 @@
import React from 'react';
import PropTypes from 'prop-types';
import { CDataTable } from '@coreui/react';
const RadioAnalysisTable = ({ data, loading }) => {
const columns = [
{ key: 'radio', label: 'R', _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 },
{ key: 'txPower', label: 'Tx Power', _style: { width: '9%' }, sorter: false },
{ key: 'activeMs', label: 'Active MS', _style: { width: '23%' }, sorter: false },
{ key: 'busyMs', label: 'Busy MS', _style: { width: '23%' }, sorter: false },
{ key: 'receiveMs', label: 'Receive MS', _style: { width: '23%' }, sorter: false },
];
const centerIfEmpty = (value) => (
<td className={!value || value === '' || value === '-' ? 'text-center' : ''}>{value}</td>
);
return (
<CDataTable
fields={columns}
items={data}
hover
border
loading={loading}
sorter
sorterValue={{ column: 'radio', asc: true }}
scopedSlots={{
noise: (item) => centerIfEmpty(item.noise),
}}
/>
);
};
RadioAnalysisTable.propTypes = {
data: PropTypes.instanceOf(Object).isRequired,
loading: PropTypes.bool.isRequired,
};
export default RadioAnalysisTable;

View File

@@ -0,0 +1,229 @@
{
"UUID": 1626408268,
"data": {
"interfaces": [
{
"counters": {
"collisions": 0,
"multicast": 2234,
"rx_bytes": 258222,
"rx_dropped": 0,
"rx_errors": 0,
"rx_packets": 2574,
"tx_bytes": 1254,
"tx_dropped": 0,
"tx_errors": 0,
"tx_packets": 1
},
"location": null,
"name": "up_none",
"uptime": 387758
},
{
"clients": [
{
"ipv6_addresses": [
"fe80:0:0:0:1e98:c1ff:fe4f:1add"
],
"mac": "1c:98:c1:4f:1a:dd",
"ports": [
"eth1"
]
},
{
"ipv6_addresses": [
"fe80:0:0:0:1050:3814:a6a0:fcd8"
],
"mac": "46:e6:b2:59:9d:6b",
"ports": [
"eth1"
]
},
{
"ipv6_addresses": [
"fe80:0:0:0:1849:6d1f:1bde:c932"
],
"mac": "6e:45:97:3d:ad:fc",
"ports": [
"eth1"
]
},
{
"ipv6_addresses": [
"fe80:0:0:0:1879:8133:b53a:e191"
],
"mac": "78:4f:43:5f:37:e0",
"ports": [
"eth1"
]
},
{
"ipv4_addresses": [
"10.0.0.1"
],
"ipv6_addresses": [
"fe80:0:0:0:9a9d:5dff:fea3:f4bd",
"fe80:0:0:0:226:86ff:fee8:5c16"
],
"mac": "98:9d:5d:a3:f4:bd",
"ports": [
"eth1"
]
},
{
"ipv6_addresses": [
"fe80:0:0:0:a677:33ff:febb:acb2"
],
"mac": "a4:77:33:bb:ac:b2",
"ports": [
"eth1"
]
},
{
"ipv6_addresses": [
"fe80:0:0:0:141e:c84:e2ac:7416"
],
"mac": "ca:1c:85:a9:ed:24",
"ports": [
"eth1"
]
},
{
"ipv6_addresses": [
"fe80:0:0:0:f882:1877:a036:b28d"
],
"mac": "dc:41:a9:f0:1d:0b",
"ports": [
"eth1"
]
},
{
"ipv6_addresses": [
"fe80:0:0:0:105b:f1b8:883b:11d8"
],
"mac": "f2:6c:fa:68:95:05",
"ports": [
"eth1"
]
}
],
"counters": {
"collisions": 0,
"multicast": 2234,
"rx_bytes": 258222,
"rx_dropped": 0,
"rx_errors": 0,
"rx_packets": 2574,
"tx_bytes": 1254,
"tx_dropped": 0,
"tx_errors": 0,
"tx_packets": 1
},
"dns_servers": [
"24.201.245.77",
"24.200.0.1",
"24.53.0.2"
],
"ipv4": {
"addresses": [
"10.0.0.99/24"
],
"leasetime": 172800
},
"location": "/interfaces/0",
"name": "up0v0",
"ssids": [
{
"mode": "ap",
"radio": {
"$ref": "#/radios/0"
},
"ssid": "OpenWifi"
},
{
"mode": "ap",
"radio": {
"$ref": "#/radios/2"
},
"ssid": "OpenWifi"
}
],
"uptime": 387753
},
{
"counters": {
"collisions": 0,
"multicast": 0,
"rx_bytes": 0,
"rx_dropped": 0,
"rx_errors": 0,
"rx_packets": 0,
"tx_bytes": 1058,
"tx_dropped": 0,
"tx_errors": 0,
"tx_packets": 5
},
"ipv4": {
"addresses": [
"192.168.1.1/24"
]
},
"location": "/interfaces/1",
"name": "down1v0",
"ssids": [
{
"mode": "ap",
"radio": {
"$ref": "#/radios/0"
},
"ssid": "OpenWifi"
},
{
"mode": "ap",
"radio": {
"$ref": "#/radios/2"
},
"ssid": "OpenWifi"
}
],
"uptime": 387761
}
],
"radios": [
{
"active_ms": 387751448,
"busy_ms": 2637879,
"channel": 100,
"channel_width": "80",
"noise": 4294967193,
"receive_ms": 485,
"transmit_ms": 2634150,
"tx_power": 24
},
{
"active_ms": 387751962,
"busy_ms": 4379945,
"channel": 36,
"channel_width": "80",
"noise": 4294967192,
"receive_ms": 1119,
"transmit_ms": 2642485,
"tx_power": 23
}
],
"unit": {
"load": [
7680,
3136,
576
],
"localtime": 1627043604,
"memory": {
"free": 100302848,
"total": 254529536
},
"uptime": 387785
}
},
"recorded": 1627043604
}

View File

@@ -77,32 +77,36 @@ const UserListTable = ({
<CCardHeader>
<CRow>
<CCol />
<CCol xs={1}>
<div className="text-right">
<CSelect
custom
defaultValue={usersPerPage}
onChange={(e) => setUsersPerPage(e.target.value)}
disabled={loading}
>
<option value="10">10</option>
<option value="25">25</option>
<option value="50">50</option>
</CSelect>
</div>
</CCol>
<CCol xs={1}>
<div className="text-right">
<CButton
color="primary"
variant="outline"
shape="square"
onClick={toggleCreate}
block
>
{t('user.create')}
</CButton>
</div>
<CCol xs={3}>
<CRow>
<CCol xs={6}>
<div className="text-right">
<CSelect
custom
defaultValue={usersPerPage}
onChange={(e) => setUsersPerPage(e.target.value)}
disabled={loading}
>
<option value="10">10</option>
<option value="25">25</option>
<option value="50">50</option>
</CSelect>
</div>
</CCol>
<CCol xs={6}>
<div className="text-right">
<CButton
color="primary"
variant="outline"
shape="square"
onClick={toggleCreate}
block
>
{t('user.create')}
</CButton>
</div>
</CCol>
</CRow>
</CCol>
</CRow>
</CCardHeader>

View File

@@ -1,51 +1,73 @@
import React from 'react';
import PropTypes from 'prop-types';
import { CCard, CCardBody, CCardHeader, CCol, CDataTable, CRow } from '@coreui/react';
import { CButton, CDataTable, CPopover } from '@coreui/react';
const WifiAnalysisTable = ({ t, data, loading, range, updateSelectedStats }) => {
const WifiAnalysisTable = ({ t, data, loading }) => {
const columns = [
{ key: 'radio', label: 'R', _style: { width: '2%' } },
{ key: 'channel', label: 'Ch', _style: { width: '3%' } },
{ key: 'channelWidth', label: 'ChW', _style: { width: '2%' } },
{ key: 'mode', label: t('wifi_analysis.mode'), _style: { width: '5%' } },
{ key: 'txPower', label: 'TX Power', _style: { width: '3%' } },
{ key: 'ssid', label: 'SSID', _style: { width: '10%' } },
{ key: 'rssi', label: 'RSSI', _style: { width: '5%' } },
{ key: 'rxRate', label: 'RxBitrate', _style: { width: '7%' } },
{ key: 'rxBytes', label: 'RxBytes', _style: { width: '7%' } },
{ key: 'rxMcs', label: 'Rx MCS', _style: { width: '4%' } },
{ key: 'rxNss', label: 'Rx NSS', _style: { width: '4%' } },
{ key: 'txRate', label: 'TxRate', _style: { width: '7%' } },
{ key: 'txBytes', label: 'TxBytes', _style: { width: '7%' } },
{ key: 'ipV4', label: 'IpV4', _style: { width: '17%' } },
{ key: 'ipV6', label: 'IpV6', _style: { width: '17%' } },
{ key: 'radio', label: 'R', _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: '14%' } },
{ 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: 'rxMcs', label: 'Rx MCS', _style: { width: '7%' }, sorter: false },
{ key: 'rxNss', label: 'Rx NSS', _style: { width: '7%' }, 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: '7%' }, sorter: false },
];
const centerIfEmpty = (value) => (
<td className={!value || value === '' || value === '-' ? 'text-center' : ''}>{value}</td>
);
const displayIp = (v4, v6) => {
const count = v4.length + v6.length;
const content4 = v4.map((ip) => <li>{ip}</li>);
const content6 = v6.map((ip) => <li>{ip}</li>);
const content = (
<div>
IpV4
<ul>{content4}</ul>
IpV6
<ul>{content6}</ul>
</div>
);
return (
<td className="text-center">
{count > 0 ? (
<CPopover header="Ipv4 - Ipv6 Addresses" content={content} trigger="click" interactive>
<CButton color="primary" size="sm">
{count}
</CButton>
</CPopover>
) : (
<p>{count}</p>
)}
</td>
);
};
return (
<CCard>
<CCardHeader>
<CRow>
<CCol className="text-center">
<input
type="range"
style={{ width: '80%' }}
className="form-range"
min="0"
max={range}
step="1"
onChange={(e) => updateSelectedStats(e.target.value)}
defaultValue={range}
/>
<h5>
{t('common.timestamp')}: {data[0]?.timeStamp}
</h5>
</CCol>
</CRow>
</CCardHeader>
<CCardBody>
<CDataTable fields={columns} items={data} hover border loading={loading} />
</CCardBody>
</CCard>
<CDataTable
fields={columns}
items={data}
hover
border
loading={loading}
sorter
sorterValue={{ column: 'radio', asc: true }}
scopedSlots={{
radio: (item) => <td className="text-center">{item.radio.radio}</td>,
rxMcs: (item) => centerIfEmpty(item.rxMcs),
rxNss: (item) => centerIfEmpty(item.rxNss),
ips: (item) => displayIp(item.ipV4, item.ipV6),
}}
/>
);
};
@@ -53,7 +75,5 @@ WifiAnalysisTable.propTypes = {
t: PropTypes.func.isRequired,
data: PropTypes.instanceOf(Object).isRequired,
loading: PropTypes.bool.isRequired,
range: PropTypes.number.isRequired,
updateSelectedStats: PropTypes.func.isRequired,
};
export default WifiAnalysisTable;

View File

@@ -15,6 +15,8 @@ export { default as EditUserForm } from './components/EditUserForm';
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 ApiStatusCard } from './components/ApiStatusCard';
// Pages
export { default as LoginPage } from './components/LoginPage';

View File

@@ -1,6 +1,6 @@
.sidebarImgFull {
height: 100px;
width: 230px;
height: 75px;
width: 175;
}
.sidebarImgMinimized {