mirror of
				https://github.com/optim-enterprises-bv/OptimCloud-gw-ui.git
				synced 2025-10-30 17:57:46 +00:00 
			
		
		
		
	Added commands and logs to device page
This commit is contained in:
		
							
								
								
									
										183
									
								
								src/components/DeviceCommands.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										183
									
								
								src/components/DeviceCommands.js
									
									
									
									
									
										Normal 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; | ||||
| @@ -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); | ||||
|       }); | ||||
|   }; | ||||
|  | ||||
| @@ -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>, | ||||
|   | ||||
| @@ -187,7 +187,7 @@ const DeviceListDisplay = ({ devices, loading, updateDevicesPerPage, pageCount, | ||||
|         </CCardHeader> | ||||
|         <CCardBody> | ||||
|           <CDataTable | ||||
|             items={devices} | ||||
|             items={devices ?? []} | ||||
|             fields={columns} | ||||
|             border | ||||
|             hover | ||||
|   | ||||
							
								
								
									
										183
									
								
								src/components/DeviceLogs.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										183
									
								
								src/components/DeviceLogs.js
									
									
									
									
									
										Normal 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; | ||||
| @@ -39,9 +39,9 @@ const FirmwareUpgradeModal = ({ show, toggleModal }) => { | ||||
|       valid = false; | ||||
|     } | ||||
|  | ||||
|     if (chosenDate.trim() === ''){ | ||||
|     if (chosenDate.trim() === '') { | ||||
|       setValidDate(false); | ||||
|       valid = false | ||||
|       valid = false; | ||||
|     } | ||||
|     return valid; | ||||
|   }; | ||||
| @@ -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 })} | ||||
|   | ||||
| @@ -34,4 +34,4 @@ export const addDays = (date, days) => { | ||||
|   const newDate = new Date(date); | ||||
|   newDate.setDate(date.getDate() + days); | ||||
|   return newDate; | ||||
| } | ||||
| }; | ||||
|   | ||||
| @@ -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> | ||||
|   | ||||
| @@ -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 = { ...{ | ||||
|     const parameters = { | ||||
|       ...{ | ||||
|         serialNumber: selectedDeviceId, | ||||
|       when: utcDateString | ||||
|     }, ...extraParameters} | ||||
|         when: utcDateString, | ||||
|       }, | ||||
|       ...extraParameters, | ||||
|     }; | ||||
|  | ||||
|     const headers = { | ||||
|       Accept: 'application/json', | ||||
|       Authorization: `Bearer ${token}` | ||||
|     } | ||||
|       Authorization: `Bearer ${token}`, | ||||
|     }; | ||||
|  | ||||
|     axiosInstance | ||||
|       .post(`/device/${selectedDeviceId}/${action}`, parameters, { headers }) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 bourquecharles
					bourquecharles