mirror of
https://github.com/Telecominfraproject/wlan-cloud-ucentral-ui-libs.git
synced 2025-11-02 03:37:56 +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",
|
"name": "ucentral-libs",
|
||||||
"version": "0.8.24",
|
"version": "0.8.29",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "ucentral-libs",
|
"name": "ucentral-libs",
|
||||||
"version": "0.8.24",
|
"version": "0.8.29",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.14.6",
|
"@babel/core": "^7.14.6",
|
||||||
"@babel/plugin-proposal-class-properties": "^7.14.5",
|
"@babel/plugin-proposal-class-properties": "^7.14.5",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ucentral-libs",
|
"name": "ucentral-libs",
|
||||||
"version": "0.8.24",
|
"version": "0.8.29",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"source": "src/index.js",
|
"source": "src/index.js",
|
||||||
"engines": {
|
"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 { prettyDate } from '../../utils/formatting';
|
||||||
import LoadingButton from '../LoadingButton';
|
import LoadingButton from '../LoadingButton';
|
||||||
|
|
||||||
const NotesTable = ({ t, notes, addNote, loading, size }) => {
|
const NotesTable = ({ t, notes, addNote, loading, size, extraFunctionParameter }) => {
|
||||||
const [currentNote, setCurrentNote] = useState('');
|
const [currentNote, setCurrentNote] = useState('');
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
@@ -14,7 +14,7 @@ const NotesTable = ({ t, notes, addNote, loading, size }) => {
|
|||||||
];
|
];
|
||||||
|
|
||||||
const saveNote = () => {
|
const saveNote = () => {
|
||||||
addNote(currentNote);
|
addNote(currentNote, extraFunctionParameter);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -79,10 +79,12 @@ NotesTable.propTypes = {
|
|||||||
addNote: PropTypes.func.isRequired,
|
addNote: PropTypes.func.isRequired,
|
||||||
loading: PropTypes.bool.isRequired,
|
loading: PropTypes.bool.isRequired,
|
||||||
size: PropTypes.string,
|
size: PropTypes.string,
|
||||||
|
extraFunctionParameter: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
NotesTable.defaultProps = {
|
NotesTable.defaultProps = {
|
||||||
size: 'm',
|
size: 'm',
|
||||||
|
extraFunctionParameter: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default NotesTable;
|
export default NotesTable;
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { cilBan, cilCheckCircle, cilPencil, cilSync, cilTrash } from '@coreui/ic
|
|||||||
import CIcon from '@coreui/icons-react';
|
import CIcon from '@coreui/icons-react';
|
||||||
import { capitalizeFirstLetter, prettyDate } from '../../utils/formatting';
|
import { capitalizeFirstLetter, prettyDate } from '../../utils/formatting';
|
||||||
import DeleteModal from '../DeleteModal';
|
import DeleteModal from '../DeleteModal';
|
||||||
|
import Avatar from '../Avatar';
|
||||||
|
|
||||||
const UserListTable = ({
|
const UserListTable = ({
|
||||||
t,
|
t,
|
||||||
@@ -49,6 +50,7 @@ const UserListTable = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const fields = [
|
const fields = [
|
||||||
|
{ key: 'avatar', label: t(''), _style: { width: '5%' } },
|
||||||
{ key: 'email', label: t('user.login_id'), _style: { width: '10%' } },
|
{ key: 'email', label: t('user.login_id'), _style: { width: '10%' } },
|
||||||
{ key: 'name', label: t('user.name'), _style: { width: '10%' } },
|
{ key: 'name', label: t('user.name'), _style: { width: '10%' } },
|
||||||
{ key: 'userRole', label: t('user.user_role'), _style: { width: '5%' } },
|
{ key: 'userRole', label: t('user.user_role'), _style: { width: '5%' } },
|
||||||
@@ -130,6 +132,11 @@ const UserListTable = ({
|
|||||||
hover
|
hover
|
||||||
border
|
border
|
||||||
scopedSlots={{
|
scopedSlots={{
|
||||||
|
avatar: (item) => (
|
||||||
|
<td className="text-center">
|
||||||
|
<Avatar src={item.avatar} fallback={item.email} />
|
||||||
|
</td>
|
||||||
|
),
|
||||||
name: (item) => (
|
name: (item) => (
|
||||||
<td>
|
<td>
|
||||||
<p style={{ width: 'calc(10vw)' }} className="text-truncate">
|
<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>,
|
radio: (item) => <td className="text-center">{item.radio.radio}</td>,
|
||||||
rxMcs: (item) => centerIfEmpty(item.rxMcs),
|
rxMcs: (item) => centerIfEmpty(item.rxMcs),
|
||||||
rxNss: (item) => centerIfEmpty(item.rxNss),
|
rxNss: (item) => centerIfEmpty(item.rxNss),
|
||||||
|
rssi: (item) => centerIfEmpty(item.rssi),
|
||||||
ips: (item) => displayIp(item.ipV4, item.ipV6),
|
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 WifiAnalysisTable } from './components/WifiAnalysisTable';
|
||||||
export { default as RadioAnalysisTable } from './components/RadioAnalysisTable';
|
export { default as RadioAnalysisTable } from './components/RadioAnalysisTable';
|
||||||
export { default as ApiStatusCard } from './components/ApiStatusCard';
|
export { default as ApiStatusCard } from './components/ApiStatusCard';
|
||||||
|
export { default as FirmwareList } from './components/FirmwareList';
|
||||||
|
|
||||||
// Pages
|
// Pages
|
||||||
export { default as LoginPage } from './components/LoginPage';
|
export { default as LoginPage } from './components/LoginPage';
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ const Header = ({
|
|||||||
t,
|
t,
|
||||||
i18n,
|
i18n,
|
||||||
logout,
|
logout,
|
||||||
|
logo,
|
||||||
authToken,
|
authToken,
|
||||||
endpoints,
|
endpoints,
|
||||||
user,
|
user,
|
||||||
@@ -50,7 +51,7 @@ const Header = ({
|
|||||||
<CToggler inHeader className="ml-md-3 d-lg-none" onClick={toggleSidebarMobile} />
|
<CToggler inHeader className="ml-md-3 d-lg-none" onClick={toggleSidebarMobile} />
|
||||||
<CToggler inHeader className="ml-3 d-md-down-none" onClick={toggleSidebar} />
|
<CToggler inHeader className="ml-3 d-md-down-none" onClick={toggleSidebar} />
|
||||||
<CHeaderBrand className="mx-auto d-lg-none" to="/">
|
<CHeaderBrand className="mx-auto d-lg-none" to="/">
|
||||||
<CIcon name="logo" height="48" alt="Logo" />
|
<img src={logo} alt="OpenWifi" />
|
||||||
</CHeaderBrand>
|
</CHeaderBrand>
|
||||||
|
|
||||||
<CHeaderNav className="d-md-down-none mr-auto" />
|
<CHeaderNav className="d-md-down-none mr-auto" />
|
||||||
@@ -94,6 +95,7 @@ Header.propTypes = {
|
|||||||
i18n: PropTypes.instanceOf(Object).isRequired,
|
i18n: PropTypes.instanceOf(Object).isRequired,
|
||||||
logout: PropTypes.func.isRequired,
|
logout: PropTypes.func.isRequired,
|
||||||
authToken: PropTypes.string.isRequired,
|
authToken: PropTypes.string.isRequired,
|
||||||
|
logo: PropTypes.string.isRequired,
|
||||||
endpoints: PropTypes.instanceOf(Object).isRequired,
|
endpoints: PropTypes.instanceOf(Object).isRequired,
|
||||||
user: PropTypes.instanceOf(Object).isRequired,
|
user: PropTypes.instanceOf(Object).isRequired,
|
||||||
avatar: PropTypes.string.isRequired,
|
avatar: PropTypes.string.isRequired,
|
||||||
|
|||||||
@@ -29,3 +29,14 @@ export const emailToName = (email) => {
|
|||||||
}
|
}
|
||||||
return 'N/A';
|
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