Versiom 0.8.57

This commit is contained in:
BourqueCharles
2021-08-11 09:40:43 -04:00
parent 635502c9dd
commit 04ed9e8ee8
14 changed files with 463 additions and 107 deletions

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "ucentral-libs",
"version": "0.8.49",
"version": "0.8.57",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "ucentral-libs",
"version": "0.8.49",
"version": "0.8.57",
"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.49",
"version": "0.8.57",
"main": "dist/index.js",
"source": "src/index.js",
"engines": {

View File

@@ -71,6 +71,13 @@ const DeviceListTable = ({
}
};
const getShortRevision = (revision) => {
if (revision.includes(' / ')) {
return revision.split(' / ')[1];
}
return revision;
};
useEffect(() => {
document.addEventListener('keydown', escFunction, false);
@@ -261,7 +268,7 @@ const DeviceListTable = ({
</CCol>
</CRow>
</CCardHeader>
<CCardBody>
<CCardBody className="p-0">
<CDataTable
addTableClasses="ignore-overflow"
items={devices ?? []}
@@ -293,7 +300,7 @@ const DeviceListTable = ({
placement="top"
>
<div style={{ width: 'calc(22vw)' }} className="text-truncate align-middle">
{item.firmware}
{getShortRevision(item.firmware)}
</div>
</CPopover>
</td>
@@ -406,23 +413,25 @@ const DeviceListTable = ({
),
}}
/>
<ReactPaginate
previousLabel="← Previous"
nextLabel="Next →"
pageCount={pageCount}
onPageChange={updatePage}
forcePage={Number(currentPage)}
breakClassName="page-item"
breakLinkClassName="page-link"
containerClassName="pagination"
pageClassName="page-item"
pageLinkClassName="page-link"
previousClassName="page-item"
previousLinkClassName="page-link"
nextClassName="page-item"
nextLinkClassName="page-link"
activeClassName="active"
/>
<div className="pl-3">
<ReactPaginate
previousLabel="← Previous"
nextLabel="Next →"
pageCount={pageCount}
onPageChange={updatePage}
forcePage={Number(currentPage)}
breakClassName="page-item"
breakLinkClassName="page-link"
containerClassName="pagination"
pageClassName="page-item"
pageLinkClassName="page-link"
previousClassName="page-item"
previousLinkClassName="page-link"
nextClassName="page-item"
nextLinkClassName="page-link"
activeClassName="active"
/>
</div>
</CCardBody>
</CCard>
</>

View File

@@ -0,0 +1,35 @@
import React from 'react';
import { CPopover, CProgress, CProgressBar } from '@coreui/react';
import PropTypes from 'prop-types';
import { cleanBytesString } from '../../utils/formatting';
const MemoryBar = ({ t, usedBytes, totalBytes }) => {
const used = cleanBytesString(usedBytes);
const total = cleanBytesString(totalBytes);
const percentage = Math.floor((usedBytes / totalBytes) * 100);
return (
<CPopover content={t('status.used_total_memory', { used, total })}>
<CProgress>
<CProgressBar value={percentage}>
{percentage >= 25 ? t('status.percentage_used', { percentage, total }) : ''}
</CProgressBar>
<CProgressBar value={100 - percentage} color="transparent">
<div style={{ color: 'black' }}>
{percentage < 25
? t('status.percentage_free', { percentage: 100 - percentage, total })
: ''}
</div>
</CProgressBar>
</CProgress>
</CPopover>
);
};
MemoryBar.propTypes = {
t: PropTypes.func.isRequired,
usedBytes: PropTypes.number.isRequired,
totalBytes: PropTypes.number.isRequired,
};
export default React.memo(MemoryBar);

View File

@@ -0,0 +1,167 @@
import React from 'react';
import PropTypes from 'prop-types';
import {
CCard,
CCardHeader,
CRow,
CCol,
CCardBody,
CBadge,
CModalBody,
CAlert,
CPopover,
CButton,
CSpinner,
} from '@coreui/react';
import CIcon from '@coreui/icons-react';
import { cilSync } from '@coreui/icons';
import { prettyDate, compactSecondsToDetailed } from '../../utils/formatting';
import MemoryBar from './MemoryBar';
import styles from './index.module.scss';
const DeviceStatusCard = ({
t,
loading,
error,
deviceSerialNumber,
getData,
status,
lastStats,
}) => {
const transformLoad = (load) => {
if (load === undefined) return t('common.na');
return `${((load / 65536) * 100).toFixed(2)}%`;
};
if (!error) {
return (
<CCard>
<CCardHeader>
<CRow>
<CCol>
<div className="text-value-lg">
{t('status.title', { serialNumber: deviceSerialNumber })}
</div>
</CCol>
<CCol>
<div className="text-right">
<CPopover content={t('common.refresh')}>
<CButton color="secondary" onClick={getData} size="sm">
<CIcon content={cilSync} />
</CButton>
</CPopover>
</div>
</CCol>
</CRow>
</CCardHeader>
<CCardBody>
{(!lastStats || !status) && loading ? (
<div className={styles.centerContainer}>
<CSpinner className={styles.spinner} />
</div>
) : (
<div style={{ position: 'relative' }}>
<div className={styles.overlayContainer} hidden={!loading}>
<CSpinner className={styles.spinner} />
</div>
<CRow className="my-2">
<CCol md="5">{t('status.connection_status')} :</CCol>
<CCol xs="10" md="7">
{status?.connected ? (
<CBadge color="success">{t('common.connected')}</CBadge>
) : (
<CBadge color="danger">{t('common.not_connected')}</CBadge>
)}
</CCol>
</CRow>
<CRow className="my-2">
<CCol md="5">{t('status.uptime')} :</CCol>
<CCol xs="10" md="7">
{compactSecondsToDetailed(
lastStats?.unit?.uptime,
t('common.day'),
t('common.days'),
t('common.seconds'),
)}
</CCol>
</CRow>
<CRow className="my-2">
<CCol md="5">{t('status.last_contact')} :</CCol>
<CCol xs="10" md="7">
{prettyDate(status?.lastContact)}
</CCol>
</CRow>
<CRow className="my-2">
<CCol md="5">{t('status.localtime')} :</CCol>
<CCol xs="10" md="7">
{prettyDate(lastStats?.unit?.localtime)}
</CCol>
</CRow>
<CRow className="my-2">
<CCol md="5">{t('status.load_averages')} :</CCol>
<CCol xs="10" md="7">
{transformLoad(lastStats?.unit?.load[0])}
{' / '}
{transformLoad(lastStats?.unit?.load[1])}
{' / '}
{transformLoad(lastStats?.unit?.load[2])}
</CCol>
</CRow>
<CRow className="my-2">
<CCol md="5">{t('status.memory')} :</CCol>
<CCol xs="9" md="6" style={{ paddingTop: '5px' }}>
<MemoryBar
t={t}
usedBytes={
lastStats?.unit?.memory?.total && lastStats?.unit?.memory?.free
? lastStats?.unit?.memory?.total - lastStats?.unit?.memory?.free
: 0
}
totalBytes={lastStats?.unit?.memory?.total ?? 0}
/>
</CCol>
</CRow>
</div>
)}
</CCardBody>
</CCard>
);
}
return (
<CCard>
<CCardHeader>
<CRow>
<CCol>
<div className="text-value-lg">
{t('status.title', { serialNumber: deviceSerialNumber })}
</div>
</CCol>
</CRow>
</CCardHeader>
<CModalBody>
<CAlert hidden={!error} color="danger" className={styles.centerContainer}>
{t('status.error')}
</CAlert>
</CModalBody>
</CCard>
);
};
DeviceStatusCard.propTypes = {
t: PropTypes.func.isRequired,
loading: PropTypes.bool.isRequired,
error: PropTypes.bool.isRequired,
deviceSerialNumber: PropTypes.string.isRequired,
getData: PropTypes.func.isRequired,
status: PropTypes.instanceOf(Object),
lastStats: PropTypes.instanceOf(Object),
};
DeviceStatusCard.defaultProps = {
status: null,
lastStats: null,
};
export default React.memo(DeviceStatusCard);

View File

@@ -0,0 +1,20 @@
.centerContainer {
display: flex;
justify-content: center;
align-items: center;
position: relative;
}
.overlayContainer {
display: flex;
justify-content: center;
align-items: center;
position: absolute;
width: 100%;
height: 100%;
}
.spinner {
height: 50px;
width: 50px;
}

View File

@@ -12,6 +12,7 @@ import {
CPopover,
CRow,
CSelect,
CSwitch,
} from '@coreui/react';
import { prettyDate, cleanBytesString } from '../../utils/formatting';
import FirmwareDetails from '../FirmwareDetails';
@@ -33,6 +34,8 @@ const FirmwareList = ({
addNoteLoading,
updateDescription,
updateDescriptionLoading,
displayDev,
toggleDevDisplay,
}) => {
const [detailsShown, setDetailsShown] = useState([]);
const fields = [
@@ -73,45 +76,61 @@ const FirmwareList = ({
return (
<CCard>
<CCardHeader>
<CCardHeader className="py-2 px-3">
<CRow>
<CCol />
<CCol sm="1" className="pt-2 text-right">
{t('firmware.device_type')}
</CCol>
<CCol sm="2" className="text-right">
<div>
<CSelect
custom
value={selectedDeviceType}
onChange={(e) => setSelectedDeviceType(e.target.value)}
disabled={loading}
>
{deviceTypes.map((deviceType) => (
<option key={createUuid()} value={deviceType}>
{deviceType}
</option>
))}
</CSelect>
</div>
</CCol>
<CCol sm="1">
<div className="text-right">
<CSelect
custom
defaultValue={firmwarePerPage}
onChange={(e) => setFirmwarePerPage(e.target.value)}
disabled={loading}
>
<option value="10">10</option>
<option value="25">25</option>
<option value="50">50</option>
</CSelect>
</div>
<CCol sm="7">
<CRow>
<CCol sm="2" className="pt-2 text-right">
{t('firmware.device_type')}
</CCol>
<CCol sm="4" className="text-right">
<div>
<CSelect
custom
value={selectedDeviceType}
onChange={(e) => setSelectedDeviceType(e.target.value)}
disabled={loading}
>
{deviceTypes.map((deviceType) => (
<option key={createUuid()} value={deviceType}>
{deviceType}
</option>
))}
</CSelect>
</div>
</CCol>
<CCol sm="3" className="pt-2 text-center">
{t('firmware.show_dev')}
</CCol>
<CCol sm="1" className="pt-1 text-center">
<CSwitch
id="showDev"
color="primary"
defaultChecked={displayDev}
onClick={toggleDevDisplay}
size="lg"
/>
</CCol>
<CCol sm="2">
<div className="text-right">
<CSelect
custom
defaultValue={firmwarePerPage}
onChange={(e) => setFirmwarePerPage(e.target.value)}
disabled={loading}
>
<option value="10">10</option>
<option value="25">25</option>
<option value="50">50</option>
</CSelect>
</div>
</CCol>
</CRow>
</CCol>
</CRow>
</CCardHeader>
<CCardBody>
<CCardBody className="p-0">
<CDataTable
items={data}
fields={fields}
@@ -174,23 +193,25 @@ const FirmwareList = ({
),
}}
/>
<ReactPaginate
previousLabel="← Previous"
nextLabel="Next →"
pageCount={pageCount}
onPageChange={changePage}
forcePage={page.selected}
breakClassName="page-item"
breakLinkClassName="page-link"
containerClassName="pagination"
pageClassName="page-item"
pageLinkClassName="page-link"
previousClassName="page-item"
previousLinkClassName="page-link"
nextClassName="page-item"
nextLinkClassName="page-link"
activeClassName="active"
/>
<div className="pl-3">
<ReactPaginate
previousLabel="← Previous"
nextLabel="Next →"
pageCount={pageCount}
onPageChange={changePage}
forcePage={page.selected}
breakClassName="page-item"
breakLinkClassName="page-link"
containerClassName="pagination"
pageClassName="page-item"
pageLinkClassName="page-link"
previousClassName="page-item"
previousLinkClassName="page-link"
nextClassName="page-item"
nextLinkClassName="page-link"
activeClassName="active"
/>
</div>
</CCardBody>
</CCard>
);
@@ -212,6 +233,8 @@ FirmwareList.propTypes = {
addNoteLoading: PropTypes.bool.isRequired,
updateDescription: PropTypes.func.isRequired,
updateDescriptionLoading: PropTypes.bool.isRequired,
displayDev: PropTypes.bool.isRequired,
toggleDevDisplay: PropTypes.func.isRequired,
};
export default React.memo(FirmwareList);

View File

@@ -0,0 +1,25 @@
import React from 'react';
import CIcon from '@coreui/icons-react';
import PropTypes from 'prop-types';
import { CButton, CPopover } from '@coreui/react';
const HideTextButton = ({ t, toggle, show, size }) => (
<CPopover content={t('user.show_hide_password')}>
<CButton onClick={toggle} size={size} className="pt-0">
<CIcon name={show ? 'cil-envelope-open' : 'cil-envelope-closed'} />
</CButton>
</CPopover>
);
HideTextButton.propTypes = {
t: PropTypes.func.isRequired,
toggle: PropTypes.func.isRequired,
show: PropTypes.bool.isRequired,
size: PropTypes.string,
};
HideTextButton.defaultProps = {
size: 'sm',
};
export default React.memo(HideTextButton);

View File

@@ -79,9 +79,9 @@ const UserListTable = ({
<CCardHeader>
<CRow>
<CCol />
<CCol xs={3}>
<CCol xs={4}>
<CRow>
<CCol xs={6}>
<CCol xs={4}>
<div className="text-right">
<CSelect
custom
@@ -108,23 +108,18 @@ const UserListTable = ({
</CButton>
</div>
</CCol>
<CCol xs={2} className="text-right">
<CPopover content={t('common.refresh')}>
<CButton onClick={refreshUsers} color="primary" variant="outline">
<CIcon name="cil-sync" content={cilSync} />
</CButton>
</CPopover>
</CCol>
</CRow>
</CCol>
</CRow>
</CCardHeader>
<CCardBody>
<CRow className="pb-3 pr-2">
<CCol />
<CCol xs={1} className="text-right">
<div>
<CPopover content={t('common.refresh')}>
<CButton onClick={refreshUsers} color="primary" variant="outline">
<CIcon name="cil-sync" content={cilSync} />
</CButton>
</CPopover>
</div>
</CCol>
</CRow>
<CCardBody className="p-0">
<CDataTable
items={users}
fields={fields}
@@ -205,22 +200,24 @@ const UserListTable = ({
),
}}
/>
<ReactPaginate
previousLabel="← Previous"
nextLabel="Next →"
pageCount={pageCount}
onPageChange={setPage}
breakClassName="page-item"
breakLinkClassName="page-link"
containerClassName="pagination"
pageClassName="page-item"
pageLinkClassName="page-link"
previousClassName="page-item"
previousLinkClassName="page-link"
nextClassName="page-item"
nextLinkClassName="page-link"
activeClassName="active"
/>
<div className="pl-3">
<ReactPaginate
previousLabel="← Previous"
nextLabel="Next →"
pageCount={pageCount}
onPageChange={setPage}
breakClassName="page-item"
breakLinkClassName="page-link"
containerClassName="pagination"
pageClassName="page-item"
pageLinkClassName="page-link"
previousClassName="page-item"
previousLinkClassName="page-link"
nextClassName="page-item"
nextLinkClassName="page-link"
activeClassName="active"
/>
</div>
</CCardBody>
</CCard>
<DeleteModal

View File

@@ -1,6 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { CButton, CDataTable, CPopover } from '@coreui/react';
import { v4 as createUuid } from 'uuid';
const WifiAnalysisTable = ({ t, data, loading }) => {
const columns = [
@@ -25,8 +26,8 @@ const WifiAnalysisTable = ({ t, data, loading }) => {
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 content4 = v4.map((ip) => <li key={createUuid()}>{ip}</li>);
const content6 = v6.map((ip) => <li key={createUuid()}>{ip}</li>);
const content = (
<div>

View File

@@ -0,0 +1,51 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { CToast, CToastBody, CToaster, CToastHeader } from '@coreui/react';
import { v4 as createUuid } from 'uuid';
const ToastContext = React.createContext();
export const ToastProvider = ({ children }) => {
const [toasts, setToasts] = useState([]);
const addToast = ({
title = '',
body = '',
autohide = true,
closeButton = true,
color = 'info',
}) => {
setToasts([...toasts, { title, body, autohide, closeButton, color, key: createUuid() }]);
};
return (
<ToastContext.Provider value={{ addToast }}>
<div>
{children}
<CToaster position="top-right">
{toasts.map((toast) => (
<CToast
key={`toast${toast.key}`}
autohide={toast.autohide ? 5000 : null}
fade
color={toast.color}
className="text-white align-items-center"
show
>
<CToastHeader closeButton={toast.closeButton}>{toast.title}</CToastHeader>
<div className="d-flex">
<CToastBody>{toast.body}</CToastBody>
</div>
</CToast>
))}
</CToaster>
</div>
</ToastContext.Provider>
);
};
ToastProvider.propTypes = {
children: PropTypes.node.isRequired,
};
export const useToast = () => React.useContext(ToastContext);

View File

@@ -15,11 +15,13 @@ export { default as CopyToClipboardButton } from './components/CopyToClipboardBu
export { default as CreateUserForm } from './components/CreateUserForm';
export { default as DeviceFirmwareModal } from './components/DeviceFirmwareModal';
export { default as DeviceListTable } from './components/DeviceListTable';
export { default as DeviceStatusCard } from './components/DeviceStatusCard';
export { default as EditMyProfile } from './components/EditMyProfile';
export { default as EditUserForm } from './components/EditUserForm';
export { default as EditUserModal } from './components/EditUserModal';
export { default as FirmwareHistoryTable } from './components/FirmwareHistoryTable';
export { default as FirmwareList } from './components/FirmwareList';
export { default as HideTextButton } from './components/HideTextButton';
export { default as LanguageSwitcher } from './components/LanguageSwitcher';
export { default as LoadingButton } from './components/LoadingButton';
export { default as NotesTable } from './components/NotesTable';
@@ -39,3 +41,4 @@ export { default as useUser } from './hooks/useUser';
// Contexts
export { AuthProvider, useAuth } from './contexts/AuthProvider';
export { DeviceProvider, useDevice } from './contexts/DeviceProvider';
export { ToastProvider, useToast } from './contexts/ToastProvider';

View File

@@ -84,3 +84,24 @@ export const secondsToDetailed = (
return finalString;
};
export const compactSecondsToDetailed = (seconds, dayLabel, daysLabel, secondsLabel) => {
if (!seconds || seconds === 0) return `0 ${secondsLabel}`;
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 =
days === 1 ? `${finalString}${days} ${dayLabel}, ` : `${finalString}${days} ${daysLabel}, `;
finalString = `${finalString}${prettyNumber(hours)}:`;
finalString = `${finalString}${prettyNumber(minutes)}:`;
finalString = `${finalString}${prettyNumber(secondsLeft)}`;
return finalString;
};

View File

@@ -15,7 +15,6 @@ module.exports = {
umdNamedDefine: true,
},
externals: [nodeExternals()],
module: {
rules: [
{
@@ -53,6 +52,11 @@ module.exports = {
'react-dom': path.resolve(__dirname, './node_modules/react-dom'),
'prop-types': path.resolve(__dirname, './node_modules/prop-types'),
'react-router-dom': path.resolve(__dirname, './node_modules/react-router-dom'),
'@coreui/coreui': path.resolve(__dirname, './node_modules/@coreui/coreui'),
'@coreui/icons': path.resolve(__dirname, './node_modules/@coreui/icons'),
'@coreui/icons-react': path.resolve(__dirname, './node_modules/@coreui/icons-react'),
'@coreui/react': path.resolve(__dirname, './node_modules/@coreui/react'),
'@coreui/react-chartjs': path.resolve(__dirname, './node_modules/@coreui/react-chartjs'),
},
},
plugins: [new CleanWebpackPlugin()],