mirror of
https://github.com/Telecominfraproject/wlan-cloud-ucentral-ui-libs.git
synced 2025-10-29 18:02:21 +00:00
Version 0.8.29
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.24",
|
||||
"version": "0.8.29",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "ucentral-libs",
|
||||
"version": "0.8.24",
|
||||
"version": "0.8.29",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.14.6",
|
||||
"@babel/plugin-proposal-class-properties": "^7.14.5",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ucentral-libs",
|
||||
"version": "0.8.24",
|
||||
"version": "0.8.29",
|
||||
"main": "dist/index.js",
|
||||
"source": "src/index.js",
|
||||
"engines": {
|
||||
|
||||
98
src/components/FirmwareDetails/index.js
Normal file
98
src/components/FirmwareDetails/index.js
Normal file
@@ -0,0 +1,98 @@
|
||||
import React, { useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { CCard, CCardBody, CCardHeader, CCol, CCollapse, CInput, CRow } from '@coreui/react';
|
||||
import { prettyDate, cleanBytesString } from '../../utils/formatting';
|
||||
import NotesTable from '../NotesTable';
|
||||
import LoadingButton from '../LoadingButton';
|
||||
|
||||
const FirmwareDetails = ({
|
||||
t,
|
||||
show,
|
||||
item,
|
||||
addNote,
|
||||
addNoteLoading,
|
||||
updateDescription,
|
||||
updateDescriptionLoading,
|
||||
}) => {
|
||||
const [description, setDescription] = useState('');
|
||||
|
||||
const saveDescription = () => {
|
||||
updateDescription(description, item.id);
|
||||
};
|
||||
|
||||
return (
|
||||
<CCollapse show={show}>
|
||||
<CCard className="mt-3 mx-3">
|
||||
<CCardHeader>{t('firmware.details_title', { image: item.image })}</CCardHeader>
|
||||
<CCardBody>
|
||||
<CRow>
|
||||
<CCol sm="1">Created</CCol>
|
||||
<CCol sm="5">{prettyDate(item.created)}</CCol>
|
||||
<CCol sm="1">Release</CCol>
|
||||
<CCol sm="5">{item.release}</CCol>
|
||||
</CRow>
|
||||
<CRow className="my-3">
|
||||
<CCol sm="1">Image</CCol>
|
||||
<CCol sm="5">{item.image}</CCol>
|
||||
<CCol sm="1">Image Date</CCol>
|
||||
<CCol sm="5">{prettyDate(item.imageDate)}</CCol>
|
||||
</CRow>
|
||||
<CRow className="my-3">
|
||||
<CCol sm="1">Revision</CCol>
|
||||
<CCol sm="5">{item.revision}</CCol>
|
||||
<CCol sm="1">Size</CCol>
|
||||
<CCol sm="5">{cleanBytesString(item.size)}</CCol>
|
||||
</CRow>
|
||||
<CRow className="my-3">
|
||||
<CCol sm="1">URI</CCol>
|
||||
<CCol sm="5">{item.uri}</CCol>
|
||||
<CCol sm="1">Owner</CCol>
|
||||
<CCol sm="5">{item.owner === '' ? t('common.unknown') : item.owner}</CCol>
|
||||
</CRow>
|
||||
<CRow className="my-3">
|
||||
<CCol sm="1" className="mt-2">
|
||||
Description
|
||||
</CCol>
|
||||
<CCol sm="4">
|
||||
<CInput
|
||||
id="description"
|
||||
defaultValue={item.description}
|
||||
maxLength="50"
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
/>
|
||||
</CCol>
|
||||
<CCol sm="1">
|
||||
<LoadingButton
|
||||
label={t('common.save')}
|
||||
isLoadingLabel={t('common.saving')}
|
||||
isLoading={updateDescriptionLoading}
|
||||
action={saveDescription}
|
||||
disabled={updateDescriptionLoading}
|
||||
/>
|
||||
</CCol>
|
||||
<CCol>
|
||||
<NotesTable
|
||||
t={t}
|
||||
notes={item.notes}
|
||||
addNote={addNote}
|
||||
loading={addNoteLoading}
|
||||
extraFunctionParameter={item.id}
|
||||
/>
|
||||
</CCol>
|
||||
</CRow>
|
||||
</CCardBody>
|
||||
</CCard>
|
||||
</CCollapse>
|
||||
);
|
||||
};
|
||||
|
||||
FirmwareDetails.propTypes = {
|
||||
t: PropTypes.func.isRequired,
|
||||
show: PropTypes.bool.isRequired,
|
||||
item: PropTypes.instanceOf(Object).isRequired,
|
||||
addNoteLoading: PropTypes.bool.isRequired,
|
||||
addNote: PropTypes.func.isRequired,
|
||||
updateDescription: PropTypes.func.isRequired,
|
||||
updateDescriptionLoading: PropTypes.bool.isRequired,
|
||||
};
|
||||
export default React.memo(FirmwareDetails);
|
||||
179
src/components/FirmwareList/index.js
Normal file
179
src/components/FirmwareList/index.js
Normal file
@@ -0,0 +1,179 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ReactPaginate from 'react-paginate';
|
||||
import { v4 as createUuid } from 'uuid';
|
||||
import {
|
||||
CButton,
|
||||
CCard,
|
||||
CCardBody,
|
||||
CCardHeader,
|
||||
CCol,
|
||||
CDataTable,
|
||||
CRow,
|
||||
CSelect,
|
||||
} from '@coreui/react';
|
||||
import { prettyDate, cleanBytesString } from '../../utils/formatting';
|
||||
import FirmwareDetails from '../FirmwareDetails';
|
||||
|
||||
const FirmwareList = ({
|
||||
t,
|
||||
loading,
|
||||
page,
|
||||
pageCount,
|
||||
setPage,
|
||||
data,
|
||||
firmwarePerPage,
|
||||
setFirmwarePerPage,
|
||||
selectedDeviceType,
|
||||
deviceTypes,
|
||||
setSelectedDeviceType,
|
||||
addNote,
|
||||
addNoteLoading,
|
||||
updateDescription,
|
||||
updateDescriptionLoading,
|
||||
}) => {
|
||||
const [detailsShown, setDetailsShown] = useState([]);
|
||||
const fields = [
|
||||
{ key: 'created', label: t('common.created'), _style: { width: '12%' } },
|
||||
{ key: 'size', label: t('firmware.size'), _style: { width: '8%' } },
|
||||
{ key: 'revision', label: t('firmware.revision'), _style: { width: '30%' } },
|
||||
{ key: 'uri', label: 'URI' },
|
||||
{ key: 'show_details', label: '', _style: { width: '5%' } },
|
||||
];
|
||||
|
||||
const toggleDetails = (index) => {
|
||||
const position = detailsShown.indexOf(index);
|
||||
let newDetails = detailsShown.slice();
|
||||
|
||||
if (position !== -1) {
|
||||
newDetails.splice(position, 1);
|
||||
} else {
|
||||
newDetails = [...newDetails, index];
|
||||
}
|
||||
setDetailsShown(newDetails);
|
||||
};
|
||||
|
||||
const changePage = (newValue) => {
|
||||
setDetailsShown([]);
|
||||
setPage(newValue);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setDetailsShown([]);
|
||||
}, [selectedDeviceType]);
|
||||
|
||||
return (
|
||||
<CCard>
|
||||
<CCardHeader>
|
||||
<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>
|
||||
</CRow>
|
||||
</CCardHeader>
|
||||
<CCardBody>
|
||||
<CDataTable
|
||||
items={data}
|
||||
fields={fields}
|
||||
loading={loading}
|
||||
hover
|
||||
border
|
||||
scopedSlots={{
|
||||
created: (item) => <td>{prettyDate(item.created)}</td>,
|
||||
size: (item) => <td>{cleanBytesString(item.size)}</td>,
|
||||
show_details: (item, index) => (
|
||||
<td className="text-center">
|
||||
<CButton
|
||||
color="primary"
|
||||
variant={detailsShown.includes(index) ? '' : 'outline'}
|
||||
onClick={() => toggleDetails(index)}
|
||||
>
|
||||
{detailsShown.includes(index) ? t('common.hide') : t('common.details')}
|
||||
</CButton>
|
||||
</td>
|
||||
),
|
||||
details: (item, index) => (
|
||||
<FirmwareDetails
|
||||
t={t}
|
||||
show={detailsShown.includes(index)}
|
||||
item={item}
|
||||
addNote={addNote}
|
||||
addNoteLoading={addNoteLoading}
|
||||
updateDescription={updateDescription}
|
||||
updateDescriptionLoading={updateDescriptionLoading}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<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"
|
||||
/>
|
||||
</CCardBody>
|
||||
</CCard>
|
||||
);
|
||||
};
|
||||
|
||||
FirmwareList.propTypes = {
|
||||
t: PropTypes.func.isRequired,
|
||||
loading: PropTypes.bool.isRequired,
|
||||
pageCount: PropTypes.number.isRequired,
|
||||
page: PropTypes.instanceOf(Object).isRequired,
|
||||
setPage: PropTypes.func.isRequired,
|
||||
data: PropTypes.instanceOf(Array).isRequired,
|
||||
firmwarePerPage: PropTypes.string.isRequired,
|
||||
setFirmwarePerPage: PropTypes.func.isRequired,
|
||||
selectedDeviceType: PropTypes.string.isRequired,
|
||||
deviceTypes: PropTypes.instanceOf(Array).isRequired,
|
||||
setSelectedDeviceType: PropTypes.func.isRequired,
|
||||
addNote: PropTypes.func.isRequired,
|
||||
addNoteLoading: PropTypes.bool.isRequired,
|
||||
updateDescription: PropTypes.func.isRequired,
|
||||
updateDescriptionLoading: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export default React.memo(FirmwareList);
|
||||
@@ -4,7 +4,7 @@ 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 }) => {
|
||||
const NotesTable = ({ t, notes, addNote, loading, size, extraFunctionParameter }) => {
|
||||
const [currentNote, setCurrentNote] = useState('');
|
||||
|
||||
const columns = [
|
||||
@@ -14,7 +14,7 @@ const NotesTable = ({ t, notes, addNote, loading, size }) => {
|
||||
];
|
||||
|
||||
const saveNote = () => {
|
||||
addNote(currentNote);
|
||||
addNote(currentNote, extraFunctionParameter);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -79,10 +79,12 @@ NotesTable.propTypes = {
|
||||
addNote: PropTypes.func.isRequired,
|
||||
loading: PropTypes.bool.isRequired,
|
||||
size: PropTypes.string,
|
||||
extraFunctionParameter: PropTypes.string,
|
||||
};
|
||||
|
||||
NotesTable.defaultProps = {
|
||||
size: 'm',
|
||||
extraFunctionParameter: '',
|
||||
};
|
||||
|
||||
export default NotesTable;
|
||||
|
||||
@@ -16,6 +16,7 @@ import { cilBan, cilCheckCircle, cilPencil, cilSync, cilTrash } from '@coreui/ic
|
||||
import CIcon from '@coreui/icons-react';
|
||||
import { capitalizeFirstLetter, prettyDate } from '../../utils/formatting';
|
||||
import DeleteModal from '../DeleteModal';
|
||||
import Avatar from '../Avatar';
|
||||
|
||||
const UserListTable = ({
|
||||
t,
|
||||
@@ -49,6 +50,7 @@ const UserListTable = ({
|
||||
};
|
||||
|
||||
const fields = [
|
||||
{ key: 'avatar', label: t(''), _style: { width: '5%' } },
|
||||
{ key: 'email', label: t('user.login_id'), _style: { width: '10%' } },
|
||||
{ key: 'name', label: t('user.name'), _style: { width: '10%' } },
|
||||
{ key: 'userRole', label: t('user.user_role'), _style: { width: '5%' } },
|
||||
@@ -130,6 +132,11 @@ const UserListTable = ({
|
||||
hover
|
||||
border
|
||||
scopedSlots={{
|
||||
avatar: (item) => (
|
||||
<td className="text-center">
|
||||
<Avatar src={item.avatar} fallback={item.email} />
|
||||
</td>
|
||||
),
|
||||
name: (item) => (
|
||||
<td>
|
||||
<p style={{ width: 'calc(10vw)' }} className="text-truncate">
|
||||
|
||||
@@ -72,6 +72,7 @@ const WifiAnalysisTable = ({ t, data, loading }) => {
|
||||
radio: (item) => <td className="text-center">{item.radio.radio}</td>,
|
||||
rxMcs: (item) => centerIfEmpty(item.rxMcs),
|
||||
rxNss: (item) => centerIfEmpty(item.rxNss),
|
||||
rssi: (item) => centerIfEmpty(item.rssi),
|
||||
ips: (item) => displayIp(item.ipV4, item.ipV6),
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -17,6 +17,7 @@ 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';
|
||||
export { default as FirmwareList } from './components/FirmwareList';
|
||||
|
||||
// Pages
|
||||
export { default as LoginPage } from './components/LoginPage';
|
||||
|
||||
@@ -24,6 +24,7 @@ const Header = ({
|
||||
t,
|
||||
i18n,
|
||||
logout,
|
||||
logo,
|
||||
authToken,
|
||||
endpoints,
|
||||
user,
|
||||
@@ -50,7 +51,7 @@ const Header = ({
|
||||
<CToggler inHeader className="ml-md-3 d-lg-none" onClick={toggleSidebarMobile} />
|
||||
<CToggler inHeader className="ml-3 d-md-down-none" onClick={toggleSidebar} />
|
||||
<CHeaderBrand className="mx-auto d-lg-none" to="/">
|
||||
<CIcon name="logo" height="48" alt="Logo" />
|
||||
<img src={logo} alt="OpenWifi" />
|
||||
</CHeaderBrand>
|
||||
|
||||
<CHeaderNav className="d-md-down-none mr-auto" />
|
||||
@@ -94,6 +95,7 @@ Header.propTypes = {
|
||||
i18n: PropTypes.instanceOf(Object).isRequired,
|
||||
logout: PropTypes.func.isRequired,
|
||||
authToken: PropTypes.string.isRequired,
|
||||
logo: PropTypes.string.isRequired,
|
||||
endpoints: PropTypes.instanceOf(Object).isRequired,
|
||||
user: PropTypes.instanceOf(Object).isRequired,
|
||||
avatar: PropTypes.string.isRequired,
|
||||
|
||||
@@ -29,3 +29,14 @@ export const emailToName = (email) => {
|
||||
}
|
||||
return 'N/A';
|
||||
};
|
||||
|
||||
export const cleanBytesString = (bytes, decimals = 2) => {
|
||||
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
if (!bytes || bytes === 0) {
|
||||
return '0 B';
|
||||
}
|
||||
const k = 1024;
|
||||
const dm = decimals < 0 ? 0 : decimals;
|
||||
const i = parseInt(Math.floor(Math.log(bytes) / Math.log(k)), 10);
|
||||
return `${parseFloat((bytes / k ** i).toFixed(dm))} ${sizes[i]}`;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user