mirror of
https://github.com/Telecominfraproject/wlan-cloud-ucentral-ui-libs.git
synced 2025-11-02 11:48:06 +00:00
Versiom 0.8.57
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.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",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ucentral-libs",
|
||||
"version": "0.8.49",
|
||||
"version": "0.8.57",
|
||||
"main": "dist/index.js",
|
||||
"source": "src/index.js",
|
||||
"engines": {
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
|
||||
35
src/components/DeviceStatusCard/MemoryBar.js
Normal file
35
src/components/DeviceStatusCard/MemoryBar.js
Normal 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);
|
||||
167
src/components/DeviceStatusCard/index.js
Normal file
167
src/components/DeviceStatusCard/index.js
Normal 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);
|
||||
20
src/components/DeviceStatusCard/index.module.scss
Normal file
20
src/components/DeviceStatusCard/index.module.scss
Normal 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;
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
25
src/components/HideTextButton/index.js
Normal file
25
src/components/HideTextButton/index.js
Normal 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);
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
51
src/contexts/ToastProvider/index.js
Normal file
51
src/contexts/ToastProvider/index.js
Normal 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);
|
||||
@@ -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';
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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()],
|
||||
|
||||
Reference in New Issue
Block a user