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

View File

@@ -187,7 +187,7 @@ const DeviceListDisplay = ({ devices, loading, updateDevicesPerPage, pageCount,
</CCardHeader> </CCardHeader>
<CCardBody> <CCardBody>
<CDataTable <CDataTable
items={devices} items={devices ?? []}
fields={columns} fields={columns}
border border
hover 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; valid = false;
} }
if (chosenDate.trim() === ''){ if (chosenDate.trim() === '') {
setValidDate(false); setValidDate(false);
valid = false valid = false;
} }
return valid; return valid;
}; };
@@ -105,7 +105,7 @@ const FirmwareUpgradeModal = ({ show, toggleModal }) => {
const parameters = { const parameters = {
serialNumber: selectedDeviceId, serialNumber: selectedDeviceId,
when: utcDateString, when: utcDateString,
uri: firmware, uri: firmware,
}; };
axiosInstance axiosInstance
@@ -163,9 +163,7 @@ const FirmwareUpgradeModal = ({ show, toggleModal }) => {
<CInvalidFeedback>You need a date...</CInvalidFeedback> <CInvalidFeedback>You need a date...</CInvalidFeedback>
</CCol> </CCol>
</CRow> </CRow>
<div> <div>Firmware URI:</div>
Firmware URI:
</div>
<CInput <CInput
disabled={waiting} disabled={waiting}
className={('form-control', { 'is-invalid': !validFirmware })} className={('form-control', { 'is-invalid': !validFirmware })}

View File

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

View File

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

View File

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