Files
OptimCloud-gw-ui/src/components/CommandHistory/index.js
2021-07-06 14:11:08 -04:00

458 lines
15 KiB
JavaScript

/* eslint-disable-rule prefer-destructuring */
import React, { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import {
CWidgetDropdown,
CRow,
CCol,
CCollapse,
CButton,
CDataTable,
CCard,
CPopover,
} from '@coreui/react';
import CIcon from '@coreui/icons-react';
import DatePicker from 'react-widgets/DatePicker';
import { cilCloudDownload, cilSync, cilCalendarCheck } from '@coreui/icons';
import { prettyDate, dateToUnix } from 'utils/helper';
import axiosInstance from 'utils/axiosInstance';
import { useAuth } from 'contexts/AuthProvider';
import { useDevice } from 'contexts/DeviceProvider';
import eventBus from 'utils/eventBus';
import ConfirmModal from 'components/ConfirmModal';
import LoadingButton from 'components/LoadingButton';
import WifiScanResultModalWidget from 'components/WifiScanResultModal';
import DeviceCommandsCollapse from './DeviceCommandsCollapse';
import styles from './index.module.scss';
const DeviceCommands = () => {
const { t } = useTranslation();
const { currentToken, endpoints } = useAuth();
const { deviceSerialNumber } = useDevice();
// Wifiscan result related
const [chosenWifiScan, setChosenWifiScan] = useState(null);
const [showScanModal, setShowScanModal] = useState(false);
const [chosenWifiScanDate, setChosenWifiScanDate] = useState('');
// Delete modal related
const [showConfirmModal, setShowConfirmModal] = useState(false);
const [uuidDelete, setUuidDelete] = useState('');
// Main collapsible
const [collapse, setCollapse] = useState(false);
// Two other open collapsible lists
const [details, setDetails] = useState([]);
const [responses, setResponses] = useState([]);
// General states
const [commands, setCommands] = useState([]);
const [loading, setLoading] = useState(false);
const [start, setStart] = useState('');
const [end, setEnd] = useState('');
const [commandLimit, setCommandLimit] = useState(25);
// Load more button related
const [loadingMore, setLoadingMore] = useState(false);
const [showLoadingMore, setShowLoadingMore] = useState(true);
const toggle = (e) => {
setCollapse(!collapse);
e.preventDefault();
};
const toggleScanModal = () => {
setShowScanModal(!showScanModal);
};
const toggleConfirmModal = (uuid) => {
setUuidDelete(uuid);
setShowConfirmModal(!showConfirmModal);
};
const showMoreCommands = () => {
setCommandLimit(commandLimit + 50);
};
const modifyStart = (value) => {
setStart(value);
};
const modifyEnd = (value) => {
setEnd(value);
};
const deleteCommandFromList = (commandUuid) => {
const indexToDelete = commands.map((e) => e.UUID).indexOf(commandUuid);
const newCommands = commands;
newCommands.splice(indexToDelete, 1);
setCommands(newCommands);
};
const getCommands = () => {
if (loading) return;
setLoadingMore(true);
setLoading(true);
const options = {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${currentToken}`,
},
params: {
limit: commandLimit,
},
};
let extraParams = '&newest=true';
if (start !== '' && end !== '') {
const utcStart = new Date(start).toISOString();
const utcEnd = new Date(end).toISOString();
options.params.startDate = dateToUnix(utcStart);
options.params.endDate = dateToUnix(utcEnd);
extraParams = '';
}
axiosInstance
.get(
`${endpoints.ucentralgw}/api/v1/commands?serialNumber=${encodeURIComponent(
deviceSerialNumber,
)}${extraParams}`,
options,
)
.then((response) => {
setCommands(response.data.commands);
})
.catch(() => {})
.finally(() => {
setLoading(false);
setLoadingMore(false);
});
};
const downloadTrace = (uuid) => {
const options = {
headers: {
Accept: 'application/octet-stream',
Authorization: `Bearer ${currentToken}`,
},
responseType: 'arraybuffer',
};
axiosInstance
.get(
`${endpoints.ucentralgw}/api/v1/file/${uuid}?serialNumber=${deviceSerialNumber}`,
options,
)
.then((response) => {
const blob = new Blob([response.data], { type: 'application/octet-stream' });
const link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = `Trace_${uuid}.pcap`;
link.click();
});
};
const deleteCommand = async () => {
if (uuidDelete === '') {
return false;
}
const options = {
headers: {
Accept: 'application/json',
Authorization: `Bearer ${currentToken}`,
},
};
return axiosInstance
.delete(`${endpoints.ucentralgw}/api/v1/command/${uuidDelete}`, options)
.then(() => {
deleteCommandFromList(uuidDelete);
setUuidDelete('');
return true;
})
.catch(() => {
setUuidDelete('');
return false;
});
};
const toggleDetails = (item, index) => {
if (item.command === 'wifiscan') {
setChosenWifiScan(item.results.status.scan);
setChosenWifiScanDate(item.completed);
setShowScanModal(true);
} else if (item.command === 'trace' && item.waitingForFile === 0) {
downloadTrace(item.UUID);
} else {
const position = details.indexOf(index);
let newDetails = details.slice();
if (position !== -1) {
newDetails.splice(position, 1);
} else {
newDetails = [...details, index];
}
setDetails(newDetails);
}
};
const toggleResponse = (item, index) => {
const position = responses.indexOf(index);
let newResponses = responses.slice();
if (position !== -1) {
newResponses.splice(position, 1);
} else {
newResponses = [...newResponses, index];
}
setResponses(newResponses);
};
const refreshCommands = () => {
getCommands();
};
const getDetails = (command, index) => {
if (!details.includes(index)) {
return <pre className="ignore" />;
}
if (command.results) {
const result = command.results;
if (result) return <pre className="ignore">{JSON.stringify(result, null, 4)}</pre>;
}
return <pre className="ignore">{JSON.stringify(command, null, 4)}</pre>;
};
const getResponse = (commandDetails, index) => {
if (!responses.includes(index)) {
return <pre className="ignore" />;
}
return <pre className="ignore">{JSON.stringify(commandDetails, null, 4)}</pre>;
};
const columns = [
{ key: 'UUID', label: t('common.id'), _style: { width: '28%' } },
{ key: 'command', label: t('common.command'), _style: { width: '10%' } },
{ key: 'completed', label: t('common.completed'), filter: false, _style: { width: '16%' } },
{ key: 'submitted', label: t('common.submitted'), filter: false, _style: { width: '16%' } },
{ key: 'executed', label: t('common.executed'), filter: false, _style: { width: '16%' } },
{
key: 'show_buttons',
label: '',
sorter: false,
filter: false,
_style: { width: '14%' },
},
];
useEffect(() => {
if (deviceSerialNumber && start !== '' && end !== '') {
getCommands();
} else if (deviceSerialNumber && start === '' && end === '') {
getCommands();
}
}, [deviceSerialNumber, start, end]);
useEffect(() => {
eventBus.on('actionCompleted', () => refreshCommands());
return () => {
eventBus.remove('actionCompleted');
};
}, []);
useEffect(() => {
if (deviceSerialNumber) {
setCommandLimit(25);
setLoadingMore(false);
setShowLoadingMore(true);
setStart('');
setEnd('');
getCommands();
}
}, [deviceSerialNumber]);
useEffect(() => {
if (commandLimit !== 25) {
getCommands();
}
}, [commandLimit]);
useEffect(() => {
if (commands.length === 0 || (commands.length > 0 && commands.length < commandLimit)) {
setShowLoadingMore(false);
} else {
setShowLoadingMore(true);
}
}, [commands]);
return (
<CWidgetDropdown
inverse="true"
color="gradient-primary"
header={t('commands.title')}
footerSlot={
<div className={styles.footer}>
<CCollapse show={collapse}>
<CRow>
<CCol />
<CCol>
<div className={styles.alignRight}>
<CButton onClick={refreshCommands} size="sm">
<CIcon
name="cil-sync"
content={cilSync}
className={styles.whiteIcon}
size="2xl"
/>
</CButton>
</div>
</CCol>
</CRow>
<CRow className={styles.datepickerRow}>
<CCol>
From:
<DatePicker includeTime onChange={(date) => modifyStart(date)} />
</CCol>
<CCol>
To:
<DatePicker includeTime onChange={(date) => modifyEnd(date)} />
</CCol>
</CRow>
<CCard>
<div className={['overflow-auto', styles.scrollableBox].join(' ')}>
<CDataTable
loading={loading}
items={commands ?? []}
fields={columns}
className={styles.whiteIcon}
sorterValue={{ column: 'created', desc: 'true' }}
scopedSlots={{
completed: (item) => (
<td>
{item.completed && item.completed !== 0
? prettyDate(item.completed)
: 'Pending'}
</td>
),
submitted: (item) => (
<td>
{item.submitted && item.submitted !== ''
? prettyDate(item.submitted)
: 'Pending'}
</td>
),
executed: (item) => (
<td>
{item.executed && item.executed !== ''
? prettyDate(item.executed)
: 'Pending'}
</td>
),
show_buttons: (item, index) => (
<td>
<CRow>
<CCol>
<CPopover
content={
item.command === 'trace' ? t('common.download') : t('common.result')
}
>
<CButton
color="primary"
variant={details.includes(index) ? '' : 'outline'}
disabled={
item.completed === 0 ||
(item.command === 'trace' && item.waitingForFile !== 0)
}
shape="square"
size="sm"
onClick={() => {
toggleDetails(item, index);
}}
>
{item.command === 'trace' ? (
<CIcon content={cilCloudDownload} size="lg" />
) : (
<CIcon content={cilCalendarCheck} size="lg" />
)}
</CButton>
</CPopover>
</CCol>
<CCol>
<CPopover content={t('common.details')}>
<CButton
color="primary"
variant={responses.includes(index) ? '' : 'outline'}
shape="square"
size="sm"
onClick={() => {
toggleResponse(item, index);
}}
>
<CIcon name="cilList" size="lg" />
</CButton>
</CPopover>
</CCol>
<CCol>
<CPopover content={t('common.delete')}>
<CButton
color="primary"
variant="outline"
shape="square"
size="sm"
onClick={() => {
toggleConfirmModal(item.UUID, index);
}}
>
<CIcon name="cilTrash" size="lg" />
</CButton>
</CPopover>
</CCol>
</CRow>
</td>
),
details: (item, index) => (
<DeviceCommandsCollapse
details={details}
responses={responses}
index={index}
getDetails={getDetails}
getResponse={getResponse}
item={item}
/>
),
}}
/>
<CRow className={styles.loadMoreSpacing}>
{showLoadingMore && (
<LoadingButton
label="View More"
isLoadingLabel="Loading More..."
isLoading={loadingMore}
action={showMoreCommands}
variant="outline"
/>
)}
</CRow>
</div>
</CCard>
</CCollapse>
<CButton show={collapse ? 'true' : 'false'} color="transparent" onClick={toggle} block>
<CIcon
name={collapse ? 'cilChevronTop' : 'cilChevronBottom'}
className={styles.whiteIcon}
size="lg"
/>
</CButton>
</div>
}
>
<WifiScanResultModalWidget
show={showScanModal}
toggle={toggleScanModal}
scanResults={chosenWifiScan}
date={chosenWifiScanDate}
/>
<ConfirmModal show={showConfirmModal} toggle={toggleConfirmModal} action={deleteCommand} />
</CWidgetDropdown>
);
};
export default DeviceCommands;