Added commands and logs to device page

This commit is contained in:
bourquecharles
2021-05-16 12:44:14 -04:00
parent b5bd98e625
commit 2306004ff2
8 changed files with 416 additions and 36 deletions

View File

@@ -0,0 +1,183 @@
/* eslint-disable-rule prefer-destructuring */
import React, { useState, useEffect } from 'react';
import {
CWidgetDropdown,
CRow,
CCol,
CCollapse,
CButton,
CDataTable,
CCard,
CCardBody,
} from '@coreui/react';
import CIcon from '@coreui/icons-react';
import { useSelector } from 'react-redux';
import DatePicker from 'react-widgets/DatePicker';
import { cleanTimestamp, addDays } from '../utils/helper';
import axiosInstance from '../utils/axiosInstance';
import { getToken } from '../utils/authHelper';
const DeviceCommands = () => {
const [collapse, setCollapse] = useState(false);
const [details, setDetails] = useState([]);
const [commands, setCommands] = useState([]);
const [loading, setLoading] = useState(false);
const [start, setStart] = useState(addDays(new Date(), -3).toString());
const [end, setEnd] = useState(new Date().toString());
const selectedDeviceId = useSelector((state) => state.selectedDeviceId);
const toggle = (e) => {
setCollapse(!collapse);
e.preventDefault();
};
const getCommands = () => {
setLoading(true);
const utcStart = new Date(start).toISOString();
const utcEnd = new Date(end).toISOString();
const options = {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${getToken()}`,
},
params: {
startDate: utcStart,
endDate: utcEnd,
},
};
axiosInstance
.get(`/commands?serialNumber=${selectedDeviceId}`, options)
.then((response) => {
setCommands(response.data.commands);
})
.catch((error) => {
console.log(error);
console.log(error.response);
})
.finally(() => {
setLoading(false);
});
};
const toggleDetails = (index) => {
const position = details.indexOf(index);
let newDetails = details.slice();
if (position !== -1) {
newDetails.splice(position, 1);
} else {
newDetails = [...details, index];
}
setDetails(newDetails);
};
const columns = [
{ key: 'UUID', label: 'Id' },
{ key: 'command' },
{ key: 'completed' },
{
key: 'show_details',
label: '',
_style: { width: '1%' },
sorter: false,
filter: false,
},
];
useEffect(() => {
getCommands();
setStart(addDays(new Date(), -3).toString());
setEnd(new Date().toString());
}, []);
useEffect(() => {
getCommands();
}, [start, end]);
return (
<CWidgetDropdown
inverse
color="gradient-primary"
header="Device Commands"
footerSlot={
<div style={{ padding: '20px' }}>
<CCollapse show={collapse}>
<CRow style={{ marginBottom: '10px' }}>
<CCol>
<DatePicker
selected={start === '' ? new Date() : new Date(start)}
value={start === '' ? new Date() : new Date(start)}
includeTime
selectTime
onChange={(date) => setStart(date)}
/>
</CCol>
<CCol>
<DatePicker
selected={end === '' ? new Date() : new Date(end)}
value={end === '' ? new Date() : new Date(end)}
includeTime
selectTime
onChange={(date) => setEnd(date)}
/>
</CCol>
</CRow>
<CCard>
<div className="overflow-auto" style={{ height: '250px' }}>
<CDataTable
loading={loading}
items={commands ?? []}
fields={columns}
style={{ color: 'white' }}
border
sorterValue={{ column: 'completed', desc: 'true' }}
scopedSlots={{
completed: (item) => <td>{cleanTimestamp(item.completed)}</td>,
show_details: (item, index) => (
<td className="py-2">
<CButton
color="primary"
variant="outline"
shape="square"
size="sm"
onClick={() => {
toggleDetails(index);
}}
>
{details.includes(index) ? 'Hide' : 'Show'}
</CButton>
</td>
),
details: (item, index) => (
<CCollapse show={details.includes(index)}>
<CCardBody>
<h5>Details</h5>
<div>
<pre>{JSON.stringify(item.details, null, 4)}</pre>
</div>
</CCardBody>
</CCollapse>
),
}}
/>
</div>
</CCard>
</CCollapse>
<CButton show={collapse} color="transparent" onClick={toggle} block>
<CIcon
name={collapse ? 'cilChevronTop' : 'cilChevronBottom'}
style={{ color: 'white' }}
size="lg"
/>
</CButton>
</div>
}
>
<CIcon name="cilNotes" style={{ color: 'white' }} size="lg" />
</CWidgetDropdown>
);
};
export default DeviceCommands;

View File

@@ -1,6 +1,15 @@
/* eslint-disable-rule prefer-destructuring */
import React, { useState, useEffect } from 'react';
import { CWidgetProgress, CCollapse, CButton, CDataTable, CCard, CCardBody, CRow, CCol } from '@coreui/react';
import {
CWidgetProgress,
CCollapse,
CButton,
CDataTable,
CCard,
CCardBody,
CRow,
CCol,
} from '@coreui/react';
import CIcon from '@coreui/icons-react';
import { useSelector } from 'react-redux';
import DatePicker from 'react-widgets/DatePicker';
@@ -11,6 +20,7 @@ import { getToken } from '../utils/authHelper';
const DeviceHealth = () => {
const [collapse, setCollapse] = useState(false);
const [details, setDetails] = useState([]);
const [loading, setLoading] = useState(false);
const [healthChecks, setHealthChecks] = useState([]);
const [start, setStart] = useState(addDays(new Date(), -3).toString());
const [end, setEnd] = useState(new Date().toString());
@@ -24,6 +34,7 @@ const DeviceHealth = () => {
};
const getDeviceHealth = () => {
setLoading(true);
const utcStart = new Date(start).toISOString();
const utcEnd = new Date(end).toISOString();
@@ -32,10 +43,10 @@ const DeviceHealth = () => {
Accept: 'application/json',
Authorization: `Bearer ${getToken()}`,
},
params : {
params: {
startDate: utcStart,
endDate: utcEnd
}
endDate: utcEnd,
},
};
axiosInstance
@@ -46,6 +57,9 @@ const DeviceHealth = () => {
.catch((error) => {
console.log(error);
console.log(error.response);
})
.finally(() => {
setLoading(false);
});
};
@@ -80,7 +94,7 @@ const DeviceHealth = () => {
setStart(addDays(new Date(), -3).toString());
setEnd(new Date().toString());
}, []);
useEffect(() => {
getDeviceHealth();
}, [start, end]);
@@ -102,15 +116,15 @@ const DeviceHealth = () => {
footer={
<div>
<CCollapse show={collapse}>
<CRow style={{ marginBottom: '10px' }}>
<CRow style={{ marginBottom: '10px' }}>
<CCol>
<DatePicker
selected={start === '' ? new Date() : new Date(start)}
value={start === '' ? new Date() : new Date(start)}
includeTime
selectTime
onChange={(date) => setStart(date)}
/>
selected={start === '' ? new Date() : new Date(start)}
value={start === '' ? new Date() : new Date(start)}
includeTime
selectTime
onChange={(date) => setStart(date)}
/>
</CCol>
<CCol>
<DatePicker
@@ -128,6 +142,8 @@ const DeviceHealth = () => {
items={healthChecks ?? []}
fields={columns}
style={{ color: 'white' }}
loading={loading}
border
sorterValue={{ column: 'recorded', desc: 'true' }}
scopedSlots={{
recorded: (item) => <td>{cleanTimestamp(item.recorded)}</td>,

View File

@@ -187,7 +187,7 @@ const DeviceListDisplay = ({ devices, loading, updateDevicesPerPage, pageCount,
</CCardHeader>
<CCardBody>
<CDataTable
items={devices}
items={devices ?? []}
fields={columns}
border
hover

View File

@@ -0,0 +1,183 @@
/* eslint-disable-rule prefer-destructuring */
import React, { useState, useEffect } from 'react';
import {
CWidgetDropdown,
CRow,
CCol,
CCollapse,
CButton,
CDataTable,
CCard,
CCardBody,
} from '@coreui/react';
import CIcon from '@coreui/icons-react';
import { useSelector } from 'react-redux';
import DatePicker from 'react-widgets/DatePicker';
import { cleanTimestamp, addDays } from '../utils/helper';
import axiosInstance from '../utils/axiosInstance';
import { getToken } from '../utils/authHelper';
const DeviceLogs = () => {
const [collapse, setCollapse] = useState(false);
const [details, setDetails] = useState([]);
const [loading, setLoading] = useState(false);
const [logs, setLogs] = useState([]);
const [start, setStart] = useState(addDays(new Date(), -3).toString());
const [end, setEnd] = useState(new Date().toString());
const selectedDeviceId = useSelector((state) => state.selectedDeviceId);
const toggle = (e) => {
setCollapse(!collapse);
e.preventDefault();
};
const getLogs = () => {
setLoading(true);
const utcStart = new Date(start).toISOString();
const utcEnd = new Date(end).toISOString();
const options = {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${getToken()}`,
},
params: {
startDate: utcStart,
endDate: utcEnd,
},
};
axiosInstance
.get(`/device/${selectedDeviceId}/logs`, options)
.then((response) => {
setLogs(response.data.values);
})
.catch((error) => {
console.log(error);
console.log(error.response);
})
.finally(() => {
setLoading(false);
});
};
const toggleDetails = (index) => {
const position = details.indexOf(index);
let newDetails = details.slice();
if (position !== -1) {
newDetails.splice(position, 1);
} else {
newDetails = [...details, index];
}
setDetails(newDetails);
};
const columns = [
{ key: 'log' },
{ key: 'severity' },
{ key: 'recorded' },
{
key: 'show_details',
label: '',
_style: { width: '1%' },
sorter: false,
filter: false,
},
];
useEffect(() => {
getLogs();
setStart(addDays(new Date(), -3).toString());
setEnd(new Date().toString());
}, []);
useEffect(() => {
getLogs();
}, [start, end]);
return (
<CWidgetDropdown
inverse
color="gradient-info"
header="Device Logs"
footerSlot={
<div style={{ padding: '20px' }}>
<CCollapse show={collapse}>
<CRow style={{ marginBottom: '10px' }}>
<CCol>
<DatePicker
selected={start === '' ? new Date() : new Date(start)}
value={start === '' ? new Date() : new Date(start)}
includeTime
selectTime
onChange={(date) => setStart(date)}
/>
</CCol>
<CCol>
<DatePicker
selected={end === '' ? new Date() : new Date(end)}
value={end === '' ? new Date() : new Date(end)}
includeTime
selectTime
onChange={(date) => setEnd(date)}
/>
</CCol>
</CRow>
<CCard>
<div className="overflow-auto" style={{ height: '250px' }}>
<CDataTable
border
items={logs ?? []}
fields={columns}
loading={loading}
style={{ color: 'white' }}
sorterValue={{ column: 'recorded', desc: 'true' }}
scopedSlots={{
recorded: (item) => <td>{cleanTimestamp(item.recorded)}</td>,
show_details: (item, index) => (
<td className="py-2">
<CButton
color="primary"
variant="outline"
shape="square"
size="sm"
onClick={() => {
toggleDetails(index);
}}
>
{details.includes(index) ? 'Hide' : 'Show'}
</CButton>
</td>
),
details: (item, index) => (
<CCollapse show={details.includes(index)}>
<CCardBody>
<h5>Details</h5>
<div>
<pre>{JSON.stringify(item, null, 4)}</pre>
</div>
</CCardBody>
</CCollapse>
),
}}
/>
</div>
</CCard>
</CCollapse>
<CButton show={collapse} color="transparent" onClick={toggle} block>
<CIcon
name={collapse ? 'cilChevronTop' : 'cilChevronBottom'}
style={{ color: 'white' }}
size="lg"
/>
</CButton>
</div>
}
>
<CIcon name="cilList" style={{ color: 'white' }} size="lg" />
</CWidgetDropdown>
);
};
export default DeviceLogs;

View File

@@ -39,9 +39,9 @@ const FirmwareUpgradeModal = ({ show, toggleModal }) => {
valid = false;
}
if (chosenDate.trim() === ''){
if (chosenDate.trim() === '') {
setValidDate(false);
valid = false
valid = false;
}
return valid;
};
@@ -105,7 +105,7 @@ const FirmwareUpgradeModal = ({ show, toggleModal }) => {
const parameters = {
serialNumber: selectedDeviceId,
when: utcDateString,
when: utcDateString,
uri: firmware,
};
axiosInstance
@@ -163,9 +163,7 @@ const FirmwareUpgradeModal = ({ show, toggleModal }) => {
<CInvalidFeedback>You need a date...</CInvalidFeedback>
</CCol>
</CRow>
<div>
Firmware URI:
</div>
<div>Firmware URI:</div>
<CInput
disabled={waiting}
className={('form-control', { 'is-invalid': !validFirmware })}

View File

@@ -34,4 +34,4 @@ export const addDays = (date, days) => {
const newDate = new Date(date);
newDate.setDate(date.getDate() + days);
return newDate;
}
};

View File

@@ -5,6 +5,8 @@ import { CRow, CCol } from '@coreui/react';
import DeviceHealth from '../../components/DeviceHealth';
import DeviceConfiguration from '../../components/DeviceConfiguration';
import DeviceActions from '../../components/DeviceActions';
import DeviceCommands from '../../components/DeviceCommands';
import DeviceLogs from '../../components/DeviceLogs';
const DevicePage = () => {
const dispatch = useDispatch();
@@ -27,9 +29,11 @@ const DevicePage = () => {
<div className="App">
<CRow>
<CCol xs="12" sm="6">
<DeviceCommands />
<DeviceConfiguration />
</CCol>
<CCol xs="12" sm="6">
<DeviceLogs />
<DeviceHealth />
<DeviceActions />
</CCol>

View File

@@ -9,7 +9,7 @@ import {
CBadge,
CCol,
CRow,
CInvalidFeedback
CInvalidFeedback,
} from '@coreui/react';
import React, { useState, useEffect } from 'react';
import DatePicker from 'react-widgets/DatePicker';
@@ -19,14 +19,7 @@ import 'react-widgets/styles.css';
import { getToken } from '../utils/authHelper';
import axiosInstance from '../utils/axiosInstance';
const ActionModalWidget = ({
show,
toggleModal,
title,
directions,
action,
extraParameters,
}) => {
const ActionModalWidget = ({ show, toggleModal, title, directions, action, extraParameters }) => {
const [hadSuccess, setHadSuccess] = useState(false);
const [hadFailure, setHadFailure] = useState(false);
const [waiting, setWaiting] = useState(false);
@@ -37,7 +30,7 @@ const ActionModalWidget = ({
const selectedDeviceId = useSelector((state) => state.selectedDeviceId);
const formValidation = () => {
if(chosenDate === ''){
if (chosenDate === '') {
setValidDate(false);
return false;
}
@@ -89,15 +82,18 @@ const ActionModalWidget = ({
const utcDate = new Date(chosenDate);
const utcDateString = utcDate.toISOString();
const parameters = { ...{
serialNumber: selectedDeviceId,
when: utcDateString
}, ...extraParameters}
const parameters = {
...{
serialNumber: selectedDeviceId,
when: utcDateString,
},
...extraParameters,
};
const headers = {
Accept: 'application/json',
Authorization: `Bearer ${token}`
}
Authorization: `Bearer ${token}`,
};
axiosInstance
.post(`/device/${selectedDeviceId}/${action}`, parameters, { headers })