main
AdamSBlack 2022-01-05 16:55:56 +00:00
parent d0083947e5
commit 9b5ace58c5
12 changed files with 734 additions and 673 deletions

View File

@ -32,11 +32,13 @@ import CssBaseline from '@mui/material/CssBaseline';
function App() {
const theme = React.useMemo(
() =>
createTheme({
palette: {
mode: true ? 'dark' : 'light',
mode: window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light',
},
}),
[true],

View File

@ -31,7 +31,6 @@ import GoogleMapReact from 'google-map-react';
import axios from "axios"
import {context as SnackbarContext} from "./../../context/toast"
import {updateDongleInfo, athenaCommandControl} from "./../../controllers/devices"
const useStyles = makeStyles({
controlsButton: {
@ -53,18 +52,7 @@ function DeviceControls() {
}
async function athenaReboot() {
const data = await athenaCommandControl("c3a5d816", "reboot");
console.log(data);
if (data.dispatched) {
notifdispatch({type: 'NEW_TOAST', message: 'Athena(Reboot) ISSUED COMMAND', open: true})
} else if (data.hasOwnProperty("connected") && data.connected === false) {
notifdispatch({type: 'NEW_TOAST', message: 'Athena(Reboot) NOT CONNECTED', open: true})
} else {
notifdispatch({type: 'NEW_TOAST', message: 'Athena(Reboot) FAILED', open: true})
}
}
return (
@ -97,18 +85,19 @@ function DeviceControls() {
}
function DeviceLastSeenMap() {
console.log("api key", process.env)
return (
<div style={{ height: "500px", width: 'calc(100%)' }}>
<GoogleMapReact
height="100px"
bootstrapURLKeys={{ key: "AIzaSyAYGRgKRk4G9m8SPIjKUfEZG3cqqXH0enc" }}
bootstrapURLKeys={{ key: process.env.REACT_APP_GMAPS_API_KEY }}
defaultCenter={{
lat: 51.501134,
lng: -0.142318
}}
defaultZoom={17}
options={{
options={{
styles: [
{ elementType: "geometry", stylers: [{ color: "#242f3e" }] },
{ elementType: "labels.text.stroke", stylers: [{ color: "#242f3e" }] },
@ -116,7 +105,7 @@ function DeviceLastSeenMap() {
{
featureType: "administrative.locality",
elementType: "labels.text.fill",
stylers: [{ color: "#d59563" }],
stylers: [{ color: "#d59563" }],
},
{
featureType: "poi",
@ -196,7 +185,7 @@ function DeviceLastSeenMap() {
}
export default function SignIn() {
export default function SignIn(props) {
const classes = useStyles();
const [ state, dispatch ] = useContext(DeviceContext)
@ -215,9 +204,7 @@ export default function SignIn() {
<Grid container style={{padding: 30}}>
<Grid item xs={12}>
<DeviceControls />
</Grid>
<Grid item xs={12}>
<DeviceLastSeenMap />
@ -225,7 +212,7 @@ export default function SignIn() {
<Grid item xs={12}>
<DrivesTable />
<DrivesTable dongleId={props.device.dongle_id}/>
</Grid>
</Grid>

View File

@ -34,38 +34,26 @@ const stylezz = {
function timeSince(date) {
var seconds = Math.floor((new Date() - date) / 1000);
var interval = seconds / 31536000;
if (seconds / 86400 > 1) {
return Math.floor(seconds / 86400) + `d`;
} else if (seconds / 3600 > 1) {
return Math.floor(seconds / 3600) + `h`;
} else if (seconds / 60 > 1) {
return Math.floor(seconds / 60) + `m`;
} else {
return "just now";
}
if (interval > 1) {
return Math.floor(interval) + " years";
}
interval = seconds / 2592000;
if (interval > 1) {
return Math.floor(interval) + " months";
}
interval = seconds / 86400;
if (interval > 1) {
return Math.floor(interval) + " days";
}
interval = seconds / 3600;
if (interval > 1) {
return Math.floor(interval) + " hours";
}
interval = seconds / 60;
if (interval > 1) {
return Math.floor(interval) + " minutes";
}
return Math.floor(seconds) + "s";
}
var aDay = 24*60*60*1000;
console.log(timeSince(new Date(Date.now()-aDay)));
console.log(timeSince(new Date(Date.now()-aDay*2)));
export default function SignIn(props) {
const [state, setState] = React.useState({count: 0, last_seen: 0});
const [state, setState] = React.useState({ count: 0, last_seen: 0 });
const device = props.device;
// Reloads component to update X time ago
@ -74,20 +62,23 @@ export default function SignIn(props) {
useEffect(() => {
setInterval(() => {
setState({...state, count: state.count+1})
}, 5000)});
setState({ ...state, count: state.count + 1 })
}, 60 * 1000)
});
const deviceLastSeen = timeSince(new Date(device.last_seen));
return (
<div>
<ButtonBase style={{ padding: '10px'}}>
<Grid container spacing={3}>
<ButtonBase style={{ padding: '10px' }}>
<Grid container spacing={2}>
<Grid item xs={4}>
<img src="/c3.webp" style={{ width: "100%" }} />
</Grid>
<Grid item xs={8}>
<Grid item xs={8} style={{ textAlign: 'left' }}>
{/* <TextField
defaultValue={"SuperSkoda"}
size="small"
@ -109,25 +100,27 @@ export default function SignIn(props) {
/>*/}
<Typography variant="body2" gutterBottom>{device.dongle_id}</Typography>
<Typography variant="body2" align={"left"} gutterBottom>Dongle: {device.dongle_id}</Typography>
<Typography variant="body2" gutterBottom>Last Seen: {timeSince(new Date(device.last_seen))} ago</Typography>
<div>
{device.online ?
<Chip style={{ background: '#004d40', ...stylezz }} label="Online" size="small" variant="outlined" /> :
<Chip style={{ background: '#b71c1c', ...stylezz }} label="Offline" size="small" variant="outlined" />
{device.online ?
<Chip style={{ background: '#004d40', ...stylezz }} label="Online" size="small" variant="outlined" /> :
<Chip style={{ background: '#b71c1c', ...stylezz }} label={`Offline ${deviceLastSeen}`} size="small" variant="outlined" />
}
<Chip style={{ background: '#dcedc8', ...stylezz }} label="Uploads Enabled" size="small" variant="outlined" />
<Chip style={{ background: '#004d40', ...stylezz }} label="Active" size="small" variant="outlined" />
</div>
</Grid>
</Grid>
</ButtonBase>
<Divider />
</div>
<Divider />
</div>
);
}

View File

@ -0,0 +1,242 @@
import React, { useState, useContext, useEffect } from 'react';
import PropTypes from 'prop-types';
import { alpha } from '@mui/material/styles';
import Box from '@mui/material/Box';
import Table from '@mui/material/Table';
import TableBody from '@mui/material/TableBody';
import TableCell from '@mui/material/TableCell';
import TableContainer from '@mui/material/TableContainer';
import TableHead from '@mui/material/TableHead';
import TablePagination from '@mui/material/TablePagination';
import TableRow from '@mui/material/TableRow';
import TableSortLabel from '@mui/material/TableSortLabel';
import Toolbar from '@mui/material/Toolbar';
import Typography from '@mui/material/Typography';
import Paper from '@mui/material/Paper';
import Checkbox from '@mui/material/Checkbox';
import IconButton from '@mui/material/IconButton';
import Tooltip from '@mui/material/Tooltip';
import FormControlLabel from '@mui/material/FormControlLabel';
import Switch from '@mui/material/Switch';
import FilterListIcon from '@mui/icons-material/FilterList';
import { visuallyHidden } from '@mui/utils';
import { context as DeviceContext } from "./../../../context/devices"
import * as deviceController from "./../../../controllers/devices"
import { Skeleton, ToolTip } from '@mui/material';
import DeleteIcon from '@mui/icons-material/Delete';
import FavoriteBorderIcon from '@mui/icons-material/FavoriteBorder';
import FavoriteIcon from '@mui/icons-material/Favorite';
import OpenInNewIcon from '@mui/icons-material/OpenInNew';
const rows = [
];
const headCells = [
{
id: 'identifier',
numeric: false,
disablePadding: true,
label: 'Identifier',
},
{
id: 'car',
numeric: false,
disablePadding: false,
label: 'Car',
},
{
id: 'version',
numeric: false,
disablePadding: false,
label: 'Version',
},
{
id: 'filesize',
numeric: false,
disablePadding: false,
label: 'Filesize',
},
{
id: 'duration',
numeric: false,
disablePadding: false,
label: 'Duration',
},
{
id: 'distance_meters',
numeric: false,
disablePadding: false,
label: 'Distance',
},
{
id: 'upload_complete',
numeric: false,
disablePadding: false,
label: 'Uploaded',
},
{
id: 'is_processed',
numeric: false,
disablePadding: false,
label: 'Processed',
},
{
id: 'upload_date',
numeric: false,
disablePadding: false,
label: 'Uploaded at',
},
];
function formatDate(timestampMs) {
return new Date(timestampMs).toISOString().replace(/T/, ' ').replace(/\..+/, '');
}
function formatDuration(durationSeconds) {
durationSeconds = Math.round(durationSeconds);
const secs = durationSeconds % 60;
let mins = Math.floor(durationSeconds / 60);
let hours = Math.floor(mins / 60);
mins = mins % 60;
const days = Math.floor(hours / 24);
hours = hours % 24;
let response = '';
if (days > 0) response += days + 'd ';
if (hours > 0 || days > 0) response += hours + 'h ';
if (hours > 0 || days > 0 || mins > 0) response += mins + 'm ';
response += secs + 's';
return response;
}
function loading() {
return (
<TableRow>
<TableCell ><Skeleton animation="wave" /></TableCell>
<TableCell ><Skeleton animation="wave" /></TableCell>
<TableCell ><Skeleton animation="wave" /></TableCell>
<TableCell ><Skeleton animation="wave" /></TableCell>
</TableRow>
)
}
export default function EnhancedTable(props) {
console.log("HELLO", props.dongleId)
const [state, dispatch] = useContext(DeviceContext)
useEffect(() => {
deviceController.getBootlogs('53331425').then((res) => {
console.log("me result:", res)
setTimeout(() => {
dispatch({ type: "update_dongle_bootlogs", dongle_id: props.dongleId, bootlogs: res.data })
}, 1)
})
}, []);
console.log("drives", state.dongles[props.dongleId])
console.log("drives", typeof state.dongles[props.dongleId])
return (
<Box sx={{ width: '100%' }}>
<Paper sx={{ width: '100%', mb: 2 }}>
<TableContainer>
<Table
sx={{ minWidth: 750 }}
aria-labelledby="tableTitle"
size={'small'}
>
<TableHead>
<TableRow>
<TableCell >Date</TableCell>
<TableCell >File</TableCell>
<TableCell >File size</TableCell>
<TableCell >Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{/* if you don't need to support IE11, you can replace the `stableSort` call with:
rows.slice().sort(getComparator(order, orderBy)) */}
{state.dongles[props.dongleId].boot ? state.dongles[props.dongleId].boot.map((row) => {
return (
<TableRow
hover
>
<TableCell >{formatDate(row.date)}</TableCell>
<TableCell >{row.name}</TableCell>
<TableCell >{Math.round(row.size / 1024) + ' MiB'}</TableCell>
<TableCell>
<Tooltip title="Open in new window">
<IconButton size="small" onClick={() => window.open(row.permalink, "_blank")}>
<OpenInNewIcon fontSize="inherit"/>
</IconButton>
</Tooltip>
<Tooltip title="Preserve">
<IconButton size="small">
<FavoriteBorderIcon fontSize="inherit"/>
</IconButton>
</Tooltip>
<Tooltip title="Delete">
<IconButton size="small">
<DeleteIcon fontSize="inherit"/>
</IconButton>
</Tooltip>
</TableCell>
</TableRow>
)
}) :
[1, 1, 1, 1, 1].map((v) => (
<TableRow
>
<TableCell ><Skeleton animation="wave" /></TableCell>
<TableCell ><Skeleton animation="wave" /></TableCell>
<TableCell ><Skeleton animation="wave" /></TableCell>
<TableCell ><Skeleton animation="wave" /></TableCell>
</TableRow>
))
}
</TableBody>
</Table>
</TableContainer>
</Paper>
</Box>
);
}

View File

@ -1,4 +1,4 @@
import React, {useState, useContext} from 'react';
import React, { useState, useContext, useEffect } from 'react';
import PropTypes from 'prop-types';
import { alpha } from '@mui/material/styles';
import Box from '@mui/material/Box';
@ -14,54 +14,27 @@ import Toolbar from '@mui/material/Toolbar';
import Typography from '@mui/material/Typography';
import Paper from '@mui/material/Paper';
import Checkbox from '@mui/material/Checkbox';
import IconButton from '@mui/material/IconButton';
import Tooltip from '@mui/material/Tooltip';
import FormControlLabel from '@mui/material/FormControlLabel';
import Switch from '@mui/material/Switch';
import DeleteIcon from '@mui/icons-material/Delete';
import FilterListIcon from '@mui/icons-material/FilterList';
import { visuallyHidden } from '@mui/utils';
import { context as DeviceContext } from "./../../../context/devices"
import * as deviceController from "./../../../controllers/devices"
import { Skeleton, ToolTip } from '@mui/material';
import Tooltip from '@mui/material/Tooltip';
import IconButton from '@mui/material/IconButton';
import DeleteIcon from '@mui/icons-material/Delete';
import FavoriteBorderIcon from '@mui/icons-material/FavoriteBorder';
import FavoriteIcon from '@mui/icons-material/Favorite';
import OpenInNewIcon from '@mui/icons-material/OpenInNew';
function createData(identifier, car, version, file_size, duration, distance_meters, upload_complete, is_processed, upload_date) {
return {
identifier, car, version, file_size, duration, distance_meters, upload_complete, is_processed, upload_date
};
}
const rows = [
createData(Math.random(), 'Skoda Rapid', '0.8.10', 1000, 500, 100009, "true", "false", "01-11-2021"),
createData(Math.random(), 'Skoda Rapid', '0.8.10', 1000, 500, 100009, "true", "false", "01-11-2022"),
];
function descendingComparator(a, b, orderBy) {
if (b[orderBy] < a[orderBy]) {
return -1;
}
if (b[orderBy] > a[orderBy]) {
return 1;
}
return 0;
}
function getComparator(order, orderBy) {
return order === 'desc'
? (a, b) => descendingComparator(a, b, orderBy)
: (a, b) => -descendingComparator(a, b, orderBy);
}
// This method is created for cross-browser compatibility, if you don't
// need to support IE11, you can use Array.prototype.sort() directly
function stableSort(array, comparator) {
const stabilizedThis = array.map((el, index) => [el, index]);
stabilizedThis.sort((a, b) => {
const order = comparator(a[0], b[0]);
if (order !== 0) {
return order;
}
return a[1] - b[1];
});
return stabilizedThis.map((el) => el[0]);
}
const headCells = [
{
id: 'identifier',
@ -117,263 +90,121 @@ const headCells = [
disablePadding: false,
label: 'Uploaded at',
},
];
function EnhancedTableHead(props) {
const { onSelectAllClick, order, orderBy, numSelected, rowCount, onRequestSort } =
props;
const createSortHandler = (property) => (event) => {
onRequestSort(event, property);
};
return (
<TableHead>
<TableRow>
<TableCell padding="checkbox">
<Checkbox
color="primary"
indeterminate={numSelected > 0 && numSelected < rowCount}
checked={rowCount > 0 && numSelected === rowCount}
onChange={onSelectAllClick}
inputProps={{
'aria-label': 'select all desserts',
}}
/>
</TableCell>
{headCells.map((headCell) => (
<TableCell
key={headCell.id}
align={headCell.numeric ? 'right' : 'left'}
padding={headCell.disablePadding ? 'none' : 'normal'}
sortDirection={orderBy === headCell.id ? order : false}
>
<TableSortLabel
active={orderBy === headCell.id}
direction={orderBy === headCell.id ? order : 'asc'}
onClick={createSortHandler(headCell.id)}
>
{headCell.label}
{orderBy === headCell.id ? (
<Box component="span" sx={visuallyHidden}>
{order === 'desc' ? 'sorted descending' : 'sorted ascending'}
</Box>
) : null}
</TableSortLabel>
</TableCell>
))}
</TableRow>
</TableHead>
);
function formatDate(timestampMs) {
return new Date(timestampMs).toISOString().replace(/T/, ' ').replace(/\..+/, '');
}
EnhancedTableHead.propTypes = {
numSelected: PropTypes.number.isRequired,
onRequestSort: PropTypes.func.isRequired,
onSelectAllClick: PropTypes.func.isRequired,
order: PropTypes.oneOf(['asc', 'desc']).isRequired,
orderBy: PropTypes.string.isRequired,
rowCount: PropTypes.number.isRequired,
};
function formatDuration(durationSeconds) {
durationSeconds = Math.round(durationSeconds);
const secs = durationSeconds % 60;
let mins = Math.floor(durationSeconds / 60);
let hours = Math.floor(mins / 60);
mins = mins % 60;
const days = Math.floor(hours / 24);
hours = hours % 24;
const EnhancedTableToolbar = (props) => {
const { numSelected } = props;
let response = '';
if (days > 0) response += days + 'd ';
if (hours > 0 || days > 0) response += hours + 'h ';
if (hours > 0 || days > 0 || mins > 0) response += mins + 'm ';
response += secs + 's';
return response;
}
function buildContent(row) {
return (
<Toolbar
sx={{
pl: { sm: 2 },
pr: { xs: 1, sm: 1 },
...(numSelected > 0 && {
bgcolor: (theme) =>
alpha(theme.palette.primary.main, theme.palette.action.activatedOpacity),
}),
}}
<TableRow
hover
>
{numSelected > 0 ? (
<Typography
sx={{ flex: '1 1 100%' }}
color="inherit"
variant="subtitle1"
component="div"
>
{numSelected} selected
</Typography>
) : (
<Typography
sx={{ flex: '1 1 100%' }}
variant="h6"
id="tableTitle"
component="div"
>
Drives
</Typography>
)}
{numSelected > 0 ? (
<TableCell >{formatDate(row.date)}</TableCell>
<TableCell >{row.name}</TableCell>
<TableCell >{Math.round(row.size / 1024) + ' MiB'}</TableCell>
<TableCell>
<Tooltip title="Open in new window">
<IconButton size="small" onClick={() => window.open(row.permalink, "_blank")}>
<OpenInNewIcon fontSize="inherit" />
</IconButton>
</Tooltip>
<Tooltip title="Preserve">
<IconButton size="small">
<FavoriteBorderIcon fontSize="inherit" />
</IconButton>
</Tooltip>
<Tooltip title="Delete">
<IconButton>
<DeleteIcon />
<IconButton size="small">
<DeleteIcon fontSize="inherit" />
</IconButton>
</Tooltip>
) : (
<Tooltip title="Filter list">
<IconButton>
<FilterListIcon />
</IconButton>
</Tooltip>
)}
</Toolbar>
);
};
EnhancedTableToolbar.propTypes = {
numSelected: PropTypes.number.isRequired,
};
</TableCell>
</TableRow>
)
}
export default function EnhancedTable() {
const [order, setOrder] = React.useState('asc');
const [orderBy, setOrderBy] = React.useState('calories');
const [selected, setSelected] = React.useState([]);
const [page, setPage] = React.useState(0);
const [rowsPerPage, setRowsPerPage] = React.useState(5);
function loading() {
return (
<TableRow>
<TableCell ><Skeleton animation="wave" /></TableCell>
<TableCell ><Skeleton animation="wave" /></TableCell>
<TableCell ><Skeleton animation="wave" /></TableCell>
<TableCell ><Skeleton animation="wave" /></TableCell>
</TableRow>
)
}
const handleRequestSort = (event, property) => {
const isAsc = orderBy === property && order === 'asc';
setOrder(isAsc ? 'desc' : 'asc');
setOrderBy(property);
};
export default function EnhancedTable(props) {
console.log("HELLO", props.dongleId)
const [state, dispatch] = useContext(DeviceContext)
const handleSelectAllClick = (event) => {
if (event.target.checked) {
const newSelecteds = rows.map((n) => n.name);
setSelected(newSelecteds);
return;
}
setSelected([]);
};
useEffect(() => {
deviceController.getCrashlogs('53331425').then((res) => {
dispatch({ type: "update_dongle_bootlogs", dongle_id: props.dongleId, bootlogs: res.data })
})
const handleClick = (event, name) => {
const selectedIndex = selected.indexOf(name);
let newSelected = [];
}, []);
if (selectedIndex === -1) {
newSelected = newSelected.concat(selected, name);
} else if (selectedIndex === 0) {
newSelected = newSelected.concat(selected.slice(1));
} else if (selectedIndex === selected.length - 1) {
newSelected = newSelected.concat(selected.slice(0, -1));
} else if (selectedIndex > 0) {
newSelected = newSelected.concat(
selected.slice(0, selectedIndex),
selected.slice(selectedIndex + 1),
);
}
setSelected(newSelected);
};
const handleChangePage = (event, newPage) => {
setPage(newPage);
};
const handleChangeRowsPerPage = (event) => {
setRowsPerPage(parseInt(event.target.value, 10));
setPage(0);
};
const isSelected = (name) => selected.indexOf(name) !== -1;
// Avoid a layout jump when reaching the last page with empty rows.
const emptyRows =
page > 0 ? Math.max(0, (1 + page) * rowsPerPage - rows.length) : 0;
console.log("drives", state.dongles[props.dongleId])
console.log("drives", typeof state.dongles[props.dongleId])
return (
<Box sx={{ width: '100%' }}>
<Paper sx={{ width: '100%', mb: 2 }}>
<EnhancedTableToolbar numSelected={selected.length} />
<TableContainer>
<Table
sx={{ minWidth: 750 }}
aria-labelledby="tableTitle"
size={'small'}
>
<EnhancedTableHead
numSelected={selected.length}
order={order}
orderBy={orderBy}
onSelectAllClick={handleSelectAllClick}
onRequestSort={handleRequestSort}
rowCount={rows.length}
/>
<TableHead>
<TableRow>
<TableCell >Date</TableCell>
<TableCell >File</TableCell>
<TableCell >File size</TableCell>
<TableCell >Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{/* if you don't need to support IE11, you can replace the `stableSort` call with:
rows.slice().sort(getComparator(order, orderBy)) */}
{stableSort(rows, getComparator(order, orderBy))
.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
.map((row, index) => {
const isItemSelected = isSelected(row.name);
const labelId = `enhanced-table-checkbox-${index}`;
{state.dongles[props.dongleId].crash ?
state.dongles[props.dongleId].crash.length > 0 ? state.dongles[props.dongleId].crash.map(buildContent) : <p> No drives </p>
:
[1, 1, 1, 1, 1].map(loading)
}
return (
<TableRow
hover
onClick={(event) => handleClick(event, row.name)}
role="checkbox"
aria-checked={isItemSelected}
tabIndex={-1}
key={row.name}
selected={isItemSelected}
>
<TableCell padding="checkbox">
<Checkbox
color="primary"
checked={isItemSelected}
inputProps={{
'aria-labelledby': labelId,
}}
/>
</TableCell>
<TableCell
id={labelId}
scope="row"
>
{row.identifier}
</TableCell>
<TableCell >{row.car}</TableCell>
<TableCell >{row.version}</TableCell>
<TableCell >{row.file_size}</TableCell>
<TableCell >{row.duration}</TableCell>
<TableCell >{row.distance_meters}</TableCell>
<TableCell >{row.upload_complete}</TableCell>
<TableCell >{row.is_processed}</TableCell>
<TableCell >{row.upload_date}</TableCell>
</TableRow>
);
})}
{emptyRows > 0 && (
<TableRow
style={{
height: (33) * emptyRows,
}}
>
<TableCell colSpan={6} />
</TableRow>
)}
</TableBody>
</Table>
</TableContainer>
<TablePagination
rowsPerPageOptions={[5, 10, 25, 10000]}
component="div"
count={rows.length}
rowsPerPage={rowsPerPage}
page={page}
onPageChange={handleChangePage}
onRowsPerPageChange={handleChangeRowsPerPage}
/>
</Paper>
</Box>
);

View File

@ -21,24 +21,70 @@ import Tab from '@mui/material/Tab';
import DrivesLogTable from "./drives";
import CrashLogsTable from "./crash";
import BootLogsTable from "./boot";
import { context as DeviceContext } from "./../../../context/devices"
import Tooltip from '@mui/material/Tooltip';
import IconButton from '@mui/material/IconButton';
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
const theme = createTheme();
function formatDate(timestampMs) {
return new Date(timestampMs).toISOString().replace(/T/, ' ').replace(/\..+/, '');
}
export default function SignIn() {
export default function SignIn(props) {
const [value, setValue] = React.useState(0);
const [state, dispatch] = useContext(DeviceContext)
const handleChange = (event, newValue) => {
setValue(newValue);
};
return (
<div className="wrapper">
if (!state.dongles[props.dongleId]) { return (<p>no</p>) }
const dongle = state.dongles[props.dongleId];
// <span style={{maxWidth: "100%", overflow: "scroll", whiteSpace:"nowrap" }}> {dongle.public_key.split(/\r?\n|\r/g).map((key) => (<p style={{margin: 0}}>{key}</p>))}</span>
sda
return (
<div className="wrapper" style={{ marginTop: '10px' }}>
<Typography variant="body1">{state.dongles[props.dongleId].dongle_id}</Typography>
<Grid container>
<Grid item xs={3}>
<div style={{ padding: '5px' }}>
<h3>Device {dongle.dongle_id} </h3>
<b>Type:</b> {dongle.device_type}<br></br>
<b>Serial:</b> {dongle.serial}<br></br>
<b>IMEI:</b> {dongle.imei}<br></br>
<b>Registered:</b> {formatDate(dongle.created)}<br></br>
<b>Last Ping:</b> {formatDate(dongle.last_ping)}<br></br>
<b>Public Key:</b> -----BEGIN PUBLIC KEY-----
<Tooltip title="Copy public key">
<IconButton onClick={() => console.log("click")}>
<ContentCopyIcon />
</IconButton>
</Tooltip>
<br></br>
<b>Stored Drives: </b> ` + drives.length + `<br></br>
<b>Quota Storage: </b>{dongle.storage_used} MB / 200000 MB
</div>
</Grid>
</Grid>
</div>
);
}

View File

@ -1,4 +1,4 @@
import React, {useState, useContext} from 'react';
import React, { useState, useContext, useEffect } from 'react';
import PropTypes from 'prop-types';
import { alpha } from '@mui/material/styles';
import Box from '@mui/material/Box';
@ -18,50 +18,22 @@ import IconButton from '@mui/material/IconButton';
import Tooltip from '@mui/material/Tooltip';
import FormControlLabel from '@mui/material/FormControlLabel';
import Switch from '@mui/material/Switch';
import DeleteIcon from '@mui/icons-material/Delete';
import FilterListIcon from '@mui/icons-material/FilterList';
import { visuallyHidden } from '@mui/utils';
import { context as DeviceContext } from "./../../../context/devices"
import * as deviceController from "./../../../controllers/devices"
import { Skeleton, ToolTip } from '@mui/material';
import DeleteIcon from '@mui/icons-material/Delete';
import FavoriteBorderIcon from '@mui/icons-material/FavoriteBorder';
import FavoriteIcon from '@mui/icons-material/Favorite';
import OpenInNewIcon from '@mui/icons-material/OpenInNew';
function createData(identifier, car, version, file_size, duration, distance_meters, upload_complete, is_processed, upload_date) {
return {
identifier, car, version, file_size, duration, distance_meters, upload_complete, is_processed, upload_date
};
}
const rows = [
createData(Math.random(), 'Skoda Rapid', '0.8.10', 1000, 500, 100009, "true", "false", "01-11-2021"),
createData(Math.random(), 'Skoda Rapid', '0.8.10', 1000, 500, 100009, "true", "false", "01-11-2022"),
];
function descendingComparator(a, b, orderBy) {
if (b[orderBy] < a[orderBy]) {
return -1;
}
if (b[orderBy] > a[orderBy]) {
return 1;
}
return 0;
}
function getComparator(order, orderBy) {
return order === 'desc'
? (a, b) => descendingComparator(a, b, orderBy)
: (a, b) => -descendingComparator(a, b, orderBy);
}
// This method is created for cross-browser compatibility, if you don't
// need to support IE11, you can use Array.prototype.sort() directly
function stableSort(array, comparator) {
const stabilizedThis = array.map((el, index) => [el, index]);
stabilizedThis.sort((a, b) => {
const order = comparator(a[0], b[0]);
if (order !== 0) {
return order;
}
return a[1] - b[1];
});
return stabilizedThis.map((el) => el[0]);
}
const headCells = [
{
id: 'identifier',
@ -117,263 +89,166 @@ const headCells = [
disablePadding: false,
label: 'Uploaded at',
},
];
function EnhancedTableHead(props) {
const { onSelectAllClick, order, orderBy, numSelected, rowCount, onRequestSort } =
props;
const createSortHandler = (property) => (event) => {
onRequestSort(event, property);
};
return (
<TableHead>
<TableRow>
<TableCell padding="checkbox">
<Checkbox
color="primary"
indeterminate={numSelected > 0 && numSelected < rowCount}
checked={rowCount > 0 && numSelected === rowCount}
onChange={onSelectAllClick}
inputProps={{
'aria-label': 'select all desserts',
}}
/>
</TableCell>
{headCells.map((headCell) => (
<TableCell
key={headCell.id}
align={headCell.numeric ? 'right' : 'left'}
padding={headCell.disablePadding ? 'none' : 'normal'}
sortDirection={orderBy === headCell.id ? order : false}
>
<TableSortLabel
active={orderBy === headCell.id}
direction={orderBy === headCell.id ? order : 'asc'}
onClick={createSortHandler(headCell.id)}
>
{headCell.label}
{orderBy === headCell.id ? (
<Box component="span" sx={visuallyHidden}>
{order === 'desc' ? 'sorted descending' : 'sorted ascending'}
</Box>
) : null}
</TableSortLabel>
</TableCell>
))}
</TableRow>
</TableHead>
);
function formatDate(timestampMs) {
return new Date(timestampMs).toISOString().replace(/T/, ' ').replace(/\..+/, '');
}
EnhancedTableHead.propTypes = {
numSelected: PropTypes.number.isRequired,
onRequestSort: PropTypes.func.isRequired,
onSelectAllClick: PropTypes.func.isRequired,
order: PropTypes.oneOf(['asc', 'desc']).isRequired,
orderBy: PropTypes.string.isRequired,
rowCount: PropTypes.number.isRequired,
};
function formatDuration(durationSeconds) {
durationSeconds = Math.round(durationSeconds);
const secs = durationSeconds % 60;
let mins = Math.floor(durationSeconds / 60);
let hours = Math.floor(mins / 60);
mins = mins % 60;
const days = Math.floor(hours / 24);
hours = hours % 24;
const EnhancedTableToolbar = (props) => {
const { numSelected } = props;
return (
<Toolbar
sx={{
pl: { sm: 2 },
pr: { xs: 1, sm: 1 },
...(numSelected > 0 && {
bgcolor: (theme) =>
alpha(theme.palette.primary.main, theme.palette.action.activatedOpacity),
}),
}}
>
{numSelected > 0 ? (
<Typography
sx={{ flex: '1 1 100%' }}
color="inherit"
variant="subtitle1"
component="div"
>
{numSelected} selected
</Typography>
) : (
<Typography
sx={{ flex: '1 1 100%' }}
variant="h6"
id="tableTitle"
component="div"
>
Drives
</Typography>
)}
{numSelected > 0 ? (
<Tooltip title="Delete">
<IconButton>
<DeleteIcon />
</IconButton>
</Tooltip>
) : (
<Tooltip title="Filter list">
<IconButton>
<FilterListIcon />
</IconButton>
</Tooltip>
)}
</Toolbar>
);
};
EnhancedTableToolbar.propTypes = {
numSelected: PropTypes.number.isRequired,
};
export default function EnhancedTable() {
const [order, setOrder] = React.useState('asc');
const [orderBy, setOrderBy] = React.useState('calories');
const [selected, setSelected] = React.useState([]);
const [page, setPage] = React.useState(0);
const [rowsPerPage, setRowsPerPage] = React.useState(5);
const handleRequestSort = (event, property) => {
const isAsc = orderBy === property && order === 'asc';
setOrder(isAsc ? 'desc' : 'asc');
setOrderBy(property);
};
const handleSelectAllClick = (event) => {
if (event.target.checked) {
const newSelecteds = rows.map((n) => n.name);
setSelected(newSelecteds);
return;
}
setSelected([]);
};
const handleClick = (event, name) => {
const selectedIndex = selected.indexOf(name);
let newSelected = [];
if (selectedIndex === -1) {
newSelected = newSelected.concat(selected, name);
} else if (selectedIndex === 0) {
newSelected = newSelected.concat(selected.slice(1));
} else if (selectedIndex === selected.length - 1) {
newSelected = newSelected.concat(selected.slice(0, -1));
} else if (selectedIndex > 0) {
newSelected = newSelected.concat(
selected.slice(0, selectedIndex),
selected.slice(selectedIndex + 1),
);
}
setSelected(newSelected);
};
const handleChangePage = (event, newPage) => {
setPage(newPage);
};
const handleChangeRowsPerPage = (event) => {
setRowsPerPage(parseInt(event.target.value, 10));
setPage(0);
};
let response = '';
if (days > 0) response += days + 'd ';
if (hours > 0 || days > 0) response += hours + 'h ';
if (hours > 0 || days > 0 || mins > 0) response += mins + 'm ';
response += secs + 's';
return response;
}
const isSelected = (name) => selected.indexOf(name) !== -1;
export default function EnhancedTable(props) {
console.log("HELLO", props.dongleId)
const [state, dispatch] = useContext(DeviceContext)
useEffect(() => {
deviceController.getDrives('53331425').then((res) => {
console.log("me result:", res)
setTimeout(() => {
dispatch({ type: "update_dongle_drive", dongle_id: props.dongleId, drives: res.data })
}, 1)
})
}, []);
console.log("drives", state.dongles[props.dongleId])
console.log("drives", typeof state.dongles[props.dongleId])
// Avoid a layout jump when reaching the last page with empty rows.
const emptyRows =
page > 0 ? Math.max(0, (1 + page) * rowsPerPage - rows.length) : 0;
return (
<Box sx={{ width: '100%' }}>
<Paper sx={{ width: '100%', mb: 2 }}>
<EnhancedTableToolbar numSelected={selected.length} />
<TableContainer>
<Table
sx={{ minWidth: 750 }}
aria-labelledby="tableTitle"
size={'small'}
>
<EnhancedTableHead
numSelected={selected.length}
order={order}
orderBy={orderBy}
onSelectAllClick={handleSelectAllClick}
onRequestSort={handleRequestSort}
rowCount={rows.length}
/>
<TableHead>
<TableRow>
<TableCell >Identifier</TableCell>
<TableCell >Car</TableCell>
<TableCell >Version</TableCell>
<TableCell >File size</TableCell>
<TableCell >Duration</TableCell>
<TableCell >Distance</TableCell>
<TableCell >uploaded</TableCell>
<TableCell >Processed</TableCell>
<TableCell >Date</TableCell>
<TableCell >Actions</TableCell>
</TableRow>
</TableHead>
<TableBody>
{/* if you don't need to support IE11, you can replace the `stableSort` call with:
rows.slice().sort(getComparator(order, orderBy)) */}
{stableSort(rows, getComparator(order, orderBy))
.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage)
.map((row, index) => {
const isItemSelected = isSelected(row.name);
const labelId = `enhanced-table-checkbox-${index}`;
{state.dongles[props.dongleId].drives ? state.dongles[props.dongleId].drives.map((row) => {
let metadata;
return (
<TableRow
hover
onClick={(event) => handleClick(event, row.name)}
role="checkbox"
aria-checked={isItemSelected}
tabIndex={-1}
key={row.name}
selected={isItemSelected}
try {
metadata = JSON.parse(row.metadata)
} catch (err) { metadata = {} }
return (
<TableRow
hover
>
<TableCell
scope="row"
>{row.identifier}</TableCell>
<TableCell >{metadata.hasOwnProperty('CarParams1') ? metadata.CarParams['CarName'] : "Glorious Skoda"}</TableCell>
<TableCell >{metadata.hasOwnProperty('InitData1') ? metadata.InitData['Version'] : "Lemon boy"}</TableCell>
<TableCell >{Math.round(row.filesize / 1024) + ' MiB'}</TableCell>
<TableCell >{formatDuration(row.duration)}</TableCell>
<TableCell >{Math.round(row.distance_meters / 1000)}</TableCell>
<TableCell >{row.upload_complete.toString()}</TableCell>
<TableCell >{row.is_processed.toString()}</TableCell>
<TableCell >{formatDate(row.drive_date)}</TableCell>
<TableCell>
<Tooltip title="Open in new window">
<IconButton size="small">
<OpenInNewIcon fontSize="inherit"/>
</IconButton>
</Tooltip>
<Tooltip title="Preserve">
<IconButton size="small">
<FavoriteBorderIcon fontSize="inherit"/>
</IconButton>
</Tooltip>
<Tooltip title="Delete">
<IconButton size="small">
<DeleteIcon fontSize="inherit"/>
</IconButton>
</Tooltip>
</TableCell>
</TableRow>
)
}) :
[1, 1, 1, 1, 1].map((v) => (
<TableRow
>
<TableCell padding="checkbox">
<Skeleton animation="wave" />
</TableCell>
<TableCell
scope="row"
>
<TableCell padding="checkbox">
<Checkbox
color="primary"
checked={isItemSelected}
inputProps={{
'aria-labelledby': labelId,
}}
/>
</TableCell>
<TableCell
id={labelId}
scope="row"
>
{row.identifier}
</TableCell>
<Skeleton animation="wave" />
</TableCell>
<TableCell ><Skeleton animation="wave" /></TableCell>
<TableCell ><Skeleton animation="wave" /></TableCell>
<TableCell ><Skeleton animation="wave" /></TableCell>
<TableCell ><Skeleton animation="wave" /></TableCell>
<TableCell ><Skeleton animation="wave" /></TableCell>
<TableCell ><Skeleton animation="wave" /></TableCell>
<TableCell ><Skeleton animation="wave" /></TableCell>
<TableCell ><Skeleton animation="wave" /></TableCell>
</TableRow>
))
}
<TableCell >{row.car}</TableCell>
<TableCell >{row.version}</TableCell>
<TableCell >{row.file_size}</TableCell>
<TableCell >{row.duration}</TableCell>
<TableCell >{row.distance_meters}</TableCell>
<TableCell >{row.upload_complete}</TableCell>
<TableCell >{row.is_processed}</TableCell>
<TableCell >{row.upload_date}</TableCell>
</TableRow>
);
})}
{emptyRows > 0 && (
<TableRow
style={{
height: (33) * emptyRows,
}}
>
<TableCell colSpan={6} />
</TableRow>
)}
</TableBody>
</Table>
</TableContainer>
<TablePagination
rowsPerPageOptions={[5, 10, 25, 10000]}
component="div"
count={rows.length}
rowsPerPage={rowsPerPage}
page={page}
onPageChange={handleChangePage}
onRowsPerPageChange={handleChangeRowsPerPage}
/>
</Paper>
</Box>
);

View File

@ -49,7 +49,7 @@ function TabPanel(props) {
export default function SignIn() {
export default function SignIn(props) {
const [value, setValue] = React.useState(0);
@ -70,19 +70,19 @@ export default function SignIn() {
</Tabs>
</Box>
<TabPanel value={value} index={0}>
<DeviceInfo />
<DeviceInfo dongleId={props.dongleId}/>
</TabPanel>
<TabPanel value={value} index={1}>
<DrivesLogTable />
<DrivesLogTable dongleId={props.dongleId}/>
</TabPanel>
<TabPanel value={value} index={2}>
<CrashLogsTable />
<CrashLogsTable dongleId={props.dongleId}/>
</TabPanel>
<TabPanel value={value} index={3}>
<BootLogsTable />
<BootLogsTable dongleId={props.dongleId}/>
</TabPanel>
<TabPanel value={value} index={4}>
<Console />
<Console dongleId={props.dongleId} />
</TabPanel>
</div>
);

View File

@ -24,6 +24,7 @@ import DeviceOverview from "./../device/overview"
import { Scrollbars } from 'rc-scrollbars';
import { context as DeviceContext } from "./../../context/devices"
const theme = createTheme();
@ -38,6 +39,8 @@ export default function SignIn() {
};
const [deviceState, deviceDispatch] = useContext(DeviceContext);
console.log("USER ADMIN STATE", deviceState)
let ok = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
return (
@ -48,7 +51,7 @@ export default function SignIn() {
<Paper style={{ minHeight: "100%", maxHeight: "100%", margin: "0" }}>
<Scrollbars autoHeight={true} autoHeightMin="calc(100vh - 14px)" audoHeightMax="calc(100% - 14px)">
<div style={{ padding: '5px' }}>
{ deviceState ? Object.keys(deviceState).map(key => <DeviceOverview device={deviceState[key]}/>) : <p>no</p> }
{ deviceState ? Object.keys(deviceState.dongles).map(key => <DeviceOverview device={deviceState.dongles[key]}/>) : <p>no</p> }
</div>
@ -62,7 +65,9 @@ export default function SignIn() {
</Grid>
<Grid item xs={12} md={8} lg={9} sm={6} xl={10}>
<DeviceData />
{ deviceState.dongles['53331425'] ? <DeviceData device={deviceState.dongles['53331425']} /> : <p>no</p> }
</Grid>
</Grid>
</div>

View File

@ -1,43 +1,131 @@
import React, { createContext, useReducer, useEffect, useRef } from "react";
import Reducer from './reducer'
import * as deviceController from "./../../controllers/devices"
function process(state, action) {
if (action.type !== "ADD_DATA") { return state }
switch (action.data.command) {
case "dongle_status":
console.log("dongle:", action.data.data.dongle_id)
return {
...state,
dongles: {
...state.dongles,
[action.data.data.dongle_id]: {
...state.dongles[action.data.data.dongle_id],
online: action.data.data.online,
last_seen: action.data.data.time,
dongle_id: action.data.data.dongle_id
}
}
}
case "get_drives": {
const initialState = {
}
default:
return state;
}
}
export const Reducer = (state, action) => {
console.log("input", state, action)
switch (action.type) {
case 'ADD_DATA':
return process(state, action);
case "fetch_all_dongles":
console.log("fetch", action)
return {
...state,
dongles: action.data
}
case "update_dongle_drive":
return {
...state,
dongles: {
...state.dongles,
[action.dongle_id]: {
...state.dongles[action.dongle_id],
drives: action.drives
}
}
}
case "update_dongle_bootlogs":
return {
...state,
dongles: {
...state.dongles,
[action.dongle_id]: {
...state.dongles[action.dongle_id],
boot: action.bootlogs
}
}
}
case "update_dongle_crashlogs":
return {
...state,
dongles: {
...state.dongles,
[action.dongle_id]: {
...state.dongles[action.dongle_id],
crash: action.crashlogs
}
}
}
default:
return state;
}
};
const initialState = {
dongles: {}
};
const Store = ({ children }) => {
console.log("STORE HAS BEEN RERENDERED")
const [state, dispatch] = useReducer(Reducer, initialState);
useEffect(() => {
const ws = new WebSocket('ws://localhost:81');
const ws = new WebSocket('ws://localhost:81');
ws.onmessage = ({ data }) => {
data = JSON.parse(data)
console.log("Message")
console.log("Message")
if (data.id) {
dispatch({ type: "ADD_DATA", id: data.id, data: data })
}
};
deviceController.getAllDevices().then((devices) => {
console.log("store", devices)
dispatch({ type: "fetch_all_dongles", data: devices })
})
return () => {
try {
ws.close();
} catch (e) {}
try {
ws.close();
} catch (e) { }
};
}, []);
}, []);
return (

View File

@ -1,37 +0,0 @@
function process(state, action) {
if (action.type !== "ADD_DATA") {return state}
if (action.data.command === "dongle_status") {
console.log("dongle:", action.data.data.dongle_id)
return {
...state,
[action.data.data.dongle_id]: {
online: action.data.data.online,
last_seen: action.data.data.time,
dongle_id: action.data.data.dongle_id
}
}
} else {
return state;
}
}
const Reducer = (state, action) => {
console.log("input", state, action)
switch (action.type) {
case 'ADD_DATA':
return process(state, action)
default:
return state;
}
};
export default Reducer;

View File

@ -1,20 +1,49 @@
import { tableBodyClasses } from "@mui/material";
import axios from "axios";
import {context as DeviceContext} from "./../context/devices"
export async function updateDongleInfo(dongleId) {
const req = await axios.get('https://localhost/realtime/dongle/c3a5d816/connected', {withCredentials: true});
const responseData = req.data;
return responseData.data
export async function getDrives(dongleId) {
const req = await axios.get(`http://localhost/retropilot/0/device/${dongleId}/drives/false`, {withCredentials: true});
return req.data
}
export async function athenaCommandControl(dongleId, command, params) {
const req = await axios.get('https://localhost/realtime/dongle/c3a5d816/send/reboot', {withCredentials: true});
const responseData = req.data;
export async function getBootlogs(dongleId) {
const req = await axios.get(`http://localhost/retropilot/0/device/${dongleId}/bootlogs`, {withCredentials: true});
return req.data
}
return responseData.data
export async function getCrashlogs(dongleId) {
const req = await axios.get(`http://localhost/retropilot/0/device/${dongleId}/crashlogs`, {withCredentials: true});
return req.data
}
}
export async function getAllDevices() {
const req = await axios.get(`http://localhost/retropilot/0/devices`, {withCredentials: true});
const responseData = req.data
let dongles = {}
if (responseData.success === true) {
responseData.data.map((object)=>{
dongles = {
...dongles,
[object.dongle_id]: {
...object,
online: false,
// Show when last connected to api instead Athena by default
last_seen: object.last_ping,
}
}
})
return dongles;
}
return null;
}