diff --git a/Birdmap.API/ClientApp/src/App.tsx b/Birdmap.API/ClientApp/src/App.tsx index 4e7d569..ff99d2e 100644 --- a/Birdmap.API/ClientApp/src/App.tsx +++ b/Birdmap.API/ClientApp/src/App.tsx @@ -1,24 +1,17 @@ -import { Box, Container, IconButton, Menu, MenuItem, MenuList, Paper, Grow, Popper } from '@material-ui/core'; -import AccountCircle from '@material-ui/icons/AccountCircle'; -import AppBar from '@material-ui/core/AppBar'; -import { positions } from '@material-ui/system'; +import { Box, Paper } from '@material-ui/core'; +import { blueGrey, grey, orange } from '@material-ui/core/colors'; import { createMuiTheme, createStyles, makeStyles, Theme } from '@material-ui/core/styles'; -import Toolbar from '@material-ui/core/Toolbar'; -import Typography from '@material-ui/core/Typography'; import { ThemeProvider } from '@material-ui/styles'; -import React, { useState, } from 'react'; -import { BrowserRouter, NavLink, Redirect, Route, Switch, Link } from 'react-router-dom'; -import BirdmapTitle from './components/appBar/BirdmapTitle'; +import React, { useState } from 'react'; +import { BrowserRouter, Redirect, Route, Switch } from 'react-router-dom'; +import BirdmapBar from './components/appBar/BirdmapBar'; import Auth from './components/auth/Auth'; import AuthService from './components/auth/AuthService'; -import { ClickAwayListener } from '@material-ui/core'; -import MapContainer from './components/heatmap/Heatmap'; -import Devices from './components/devices/Devices'; -import { blueGrey, blue, orange, grey } from '@material-ui/core/colors'; -import DevicesContextProvider from './contexts/DevicesContextProvider' import Dashboard from './components/dashboard/Dashboard'; +import Devices from './components/devices/Devices'; +import MapContainer from './components/heatmap/Heatmap'; import Logs from './components/logs/Logs'; - +import DevicesContextProvider from './contexts/DevicesContextProvider'; const theme = createMuiTheme({ palette: { @@ -27,14 +20,13 @@ const theme = createMuiTheme({ dark: grey[400], }, secondary: { - main: orange[200], + main: blueGrey[700], dark: blueGrey[50], } }, }); function App() { - const [authenticated, setAuthenticated] = useState(AuthService.isAuthenticated()); const [isAdmin, setIsAdmin] = useState(AuthService.isAdmin()); @@ -61,6 +53,7 @@ function App() { return ; }; + const HeatmapComponent = () => { return ( @@ -69,16 +62,46 @@ function App() { ); }; + const HeaderComponent = () => { + return ( + + ); + } + + const PredicateRoute = ({ component: Component, predicate: Predicate, ...rest }: { [x: string]: any, component: any, predicate: any }) => { + return ( + + ); + } + + const PublicRoute = ({ component: Component, ...rest }: { [x: string]: any, component: any }) => { + return ( + + ); + } + + const PrivateRoute = ({ component: Component, ...rest }: { [x: string]: any, component: any }) => { + return ( + + ); + } + + const AdminRoute = ({ component: Component, ...rest }: { [x: string]: any, component: any }) => { + return ( + + ); + } + return ( - - + + - - - + + + @@ -88,123 +111,26 @@ function App() { export default App; -const PublicRoute = ({ component: Component, ...rest }: { [x: string]: any, component: any}) => { +const PredicateRouteInternal = ({ header: HeaderComponent, body: BodyComponent, predicate: Predicate, ...rest }: { [x: string]: any, header: any, body: any, predicate: any }) => { return ( ( - - )} /> - ); -} - -const AdminRoute = ({ component: Component, authenticated: Authenticated, isAdmin: IsAdmin, ...rest }: { [x: string]: any, component: any, authenticated: any, isAdmin: any }) => { - return ( - ( - Authenticated && IsAdmin - ? + Predicate + ? : )} /> ); }; -const PrivateRoute = ({ component: Component, authenticated: Authenticated, ...rest }: { [x: string]: any, component: any, authenticated: any }) => { - return ( - ( - Authenticated - ? - : - )} /> - ); -}; - -const DefaultLayout = ({ component: Component, authenticated: Authenticated, ...rest }: { [x: string]: any, component: any, authenticated: any }) => { +const DefaultLayoutInternal = ({ header: HeaderComponent, body: BodyComponent, ...rest }: { [x: string]: any, header: any, body: any }) => { const classes = useDefaultLayoutStyles(); - const [open, setOpen] = React.useState(false); - const anchorRef = React.useRef(null); - - const handleToggle = () => { - setOpen((prevOpen) => !prevOpen); - }; - - const handleClose = (event: React.MouseEvent) => { - if (anchorRef.current && anchorRef.current.contains(event.target as HTMLElement)) { - return; - } - - setOpen(false); - }; - - const handleLogout = (event: React.MouseEvent) => { - if (anchorRef.current && anchorRef.current.contains(event.target as HTMLElement)) { - return; - } - - AuthService.logout(); - setOpen(false); - }; - - function handleListKeyDown(event: React.KeyboardEvent) { - if (event.key === 'Tab') { - event.preventDefault(); - setOpen(false); - } - } - - const prevOpen = React.useRef(open); - React.useEffect(() => { - if (prevOpen.current === true && open === false) { - anchorRef.current!.focus(); - } - - prevOpen.current = open; - }, [open]); - - - const renderNavLinks = () => { - return Authenticated - ? - Dashboard - {} - Devices - Heatmap - - - - - {({ TransitionProps, placement }) => ( - - - - - Logout - - - - - )} - - - : null; - }; return ( - - - - - {renderNavLinks()} - - - - - + + + + + ); @@ -212,46 +138,12 @@ const DefaultLayout = ({ component: Component, authenticated: Authenticated, ... const useDefaultLayoutStyles = makeStyles((theme: Theme) => createStyles({ - bar_root: { + header: { height: '7%', }, - box_root: { + body: { backgroundColor: theme.palette.primary.dark, height: '93%', - }, - typo: { - marginLeft: 'auto', - color: 'white', - }, - nav_menu: { - display: 'flex', - flexDirection: 'row', - alignItems: 'center', - }, - nav_menu_icon: { - color: 'inherit', - marginLeft: '24px', - '&:hover': { - color: 'inherit', - } - }, - nav_menu_item: { - textDecoration: 'none', - fontWeight: 'normal', - color: 'inherit', - marginLeft: '24px', - '&:hover': { - color: 'inherit', - } - }, - nav_menu_item_active: { - textDecoration: 'underline', - fontWeight: 'bold', - color: 'inherit', - marginLeft: '24px', - '&:hover': { - color: 'inherit', - } - }, + } }), ); \ No newline at end of file diff --git a/Birdmap.API/ClientApp/src/components/appBar/BirdmapBar.tsx b/Birdmap.API/ClientApp/src/components/appBar/BirdmapBar.tsx new file mode 100644 index 0000000..793e4f8 --- /dev/null +++ b/Birdmap.API/ClientApp/src/components/appBar/BirdmapBar.tsx @@ -0,0 +1,136 @@ +import { ClickAwayListener, Container, createStyles, Grow, IconButton, makeStyles, MenuItem, MenuList, Paper, Popper, Theme } from '@material-ui/core'; +import AppBar from '@material-ui/core/AppBar'; +import Toolbar from '@material-ui/core/Toolbar'; +import Typography from '@material-ui/core/Typography'; +import AccountCircle from '@material-ui/icons/AccountCircle'; +import React from 'react'; +import { Link, NavLink } from 'react-router-dom'; +import BirdmapTitle from './BirdmapTitle'; + +export default function BirdmapBar(props: { onLogout: () => void; isAuthenticated: any; isAdmin: any; }) { + const classes = useAppbarStyles(); + const [open, setOpen] = React.useState(false); + const anchorRef = React.useRef(null); + + const handleToggle = () => { + setOpen((prevOpen) => !prevOpen); + }; + + const handleClose = (event: React.MouseEvent) => { + if (anchorRef.current && anchorRef.current.contains(event.target as HTMLElement)) { + return; + } + + setOpen(false); + }; + + const handleLogout = (event: React.MouseEvent) => { + if (anchorRef.current && anchorRef.current.contains(event.target as HTMLElement)) { + return; + } + + props.onLogout(); + setOpen(false); + }; + + function handleListKeyDown(event: React.KeyboardEvent) { + if (event.key === 'Tab') { + event.preventDefault(); + setOpen(false); + } + } + + const prevOpen = React.useRef(open); + React.useEffect(() => { + if (prevOpen.current === true && open === false) { + anchorRef.current!.focus(); + } + + prevOpen.current = open; + }, [open]); + + const renderNavLinks = () => { + return props.isAuthenticated + ? + Dashboard + {props.isAdmin ? Logs : null} + Devices + Heatmap + + + + + {({ TransitionProps, placement }) => ( + + + + + Logout + + + + + )} + + + : null; + }; + + return ( + + + + + {renderNavLinks()} + + + + ) +}; + + +const useAppbarStyles = makeStyles((theme: Theme) => + createStyles({ + typo: { + marginLeft: 'auto', + color: 'white', + }, + nav_menu: { + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + }, + nav_menu_icon: { + color: 'inherit', + marginLeft: '24px', + '&:hover': { + color: 'inherit', + } + }, + nav_menu_item: { + textDecoration: 'none', + fontWeight: 'normal', + color: 'inherit', + marginLeft: '24px', + '&:hover': { + color: 'inherit', + } + }, + nav_menu_item_active: { + textDecoration: 'underline', + fontWeight: 'bold', + color: 'inherit', + marginLeft: '24px', + '&:hover': { + color: 'inherit', + } + }, + }), +); diff --git a/Birdmap.API/ClientApp/src/components/heatmap/Heatmap.jsx b/Birdmap.API/ClientApp/src/components/heatmap/Heatmap.jsx index 61f83e7..e024d06 100644 --- a/Birdmap.API/ClientApp/src/components/heatmap/Heatmap.jsx +++ b/Birdmap.API/ClientApp/src/components/heatmap/Heatmap.jsx @@ -1,4 +1,5 @@ /*global google*/ +import { Box, withStyles } from '@material-ui/core'; import GoogleMapReact from 'google-map-react'; import React, { Component } from 'react'; import C from '../../common/Constants'; @@ -8,7 +9,14 @@ import DeviceMarker from './DeviceMarker'; const lat_offset = 0.000038; const lng_offset = -0.000058; -export default class MapContainer extends Component { +const styles = theme => ({ + root: { + height: '93vh', + width: '100%', + } +}); + +class MapContainer extends Component { constructor(props) { super(props); @@ -64,6 +72,8 @@ export default class MapContainer extends Component { } render() { + const {classes} = this.props; + const heatMapData = { positions: this.state.heatmapPoints, options: { @@ -92,7 +102,7 @@ export default class MapContainer extends Component { )); return ( -
+ {Markers} -
+ ); } -} \ No newline at end of file +} + +export default withStyles(styles)(MapContainer); \ No newline at end of file diff --git a/Birdmap.API/ClientApp/src/components/logs/Logs.jsx b/Birdmap.API/ClientApp/src/components/logs/Logs.jsx index 419d969..2e69fd3 100644 --- a/Birdmap.API/ClientApp/src/components/logs/Logs.jsx +++ b/Birdmap.API/ClientApp/src/components/logs/Logs.jsx @@ -1,8 +1,19 @@ -import { Button, Checkbox, List, ListItem, ListItemIcon, ListItemText, Paper } from '@material-ui/core'; +import { Box, Button, Checkbox, List, ListItem, ListItemIcon, ListItemText, Paper, withStyles } from '@material-ui/core'; +import { blueGrey } from '@material-ui/core/colors'; import React, { Component } from 'react'; import LogService from './LogService'; -export class Logs extends Component { +const styles = theme => ({ + root: { + padding: '64px', + backgroundColor: theme.palette.primary.dark, + }, + paper: { + backgroundColor: blueGrey[50], + }, +}); + +class Logs extends Component { constructor(props) { super(props) @@ -10,6 +21,7 @@ export class Logs extends Component { service: null, files: [], checked: [], + selectAllChecked: false, } } @@ -35,7 +47,18 @@ export class Logs extends Component { this.setState({checked: newChecked}); } - downloadChecked = () => { + handleSelectAllToggle = () => { + this.setState({selectAllChecked: !this.state.selectAllChecked}); + if (this.state.selectAllChecked) { + this.setState({checked: []}); + } else { + const newChecked = [...this.state.files]; + this.setState({checked: newChecked}); + } + + } + + onDownload = () => { this.state.service.getFiles(this.state.checked) .then(result => { const filename = `Logs-${new Date().toISOString()}.zip`; @@ -47,11 +70,15 @@ export class Logs extends Component { document.body.appendChild(element); element.click(); document.body.removeChild(element); + this.setState({checked: []}); + this.setState({selectAllChecked: false}); }) .catch(ex => console.log(ex)); } render() { + const { classes } = this.props; + const Files = this.state.files.map((value) => { const labelId = `checkbox-list-label-${value}`; @@ -72,16 +99,30 @@ export class Logs extends Component { }) return ( - - - {Files} - - - + + + + + + + + + + {Files} + + + + ) } } -export default Logs +export default withStyles(styles)(Logs); diff --git a/MQTTnet.TestApp.WinForm/Retained.json b/MQTTnet.TestApp.WinForm/Retained.json index 25cf512..a7f52d8 100644 --- a/MQTTnet.TestApp.WinForm/Retained.json +++ b/MQTTnet.TestApp.WinForm/Retained.json @@ -1 +1 @@ -[{"Topic":"devices/output","Payload":"eyJ0YWciOiIxN2NmOGI4Mi1lZDQ4LTQ4MDctOTI3MS0xZDlmMGY5ZDViMWYiLCJwcm9iYWJpbGl0eSI6MC43MjA0MzAyMzAxMjU5ODUyfQ==","QualityOfServiceLevel":1,"Retain":true,"UserProperties":null,"ContentType":null,"ResponseTopic":null,"PayloadFormatIndicator":null,"MessageExpiryInterval":null,"TopicAlias":null,"CorrelationData":null,"SubscriptionIdentifiers":null}] \ No newline at end of file +[{"Topic":"devices/output","Payload":"eyJ0YWciOiJkNDhhODAwOC0wYjU2LTRkYzAtODYxMy0zMWY0MDY4OTk0ZDciLCJwcm9iYWJpbGl0eSI6MC4yNTMxNDI4NDgyNjMwOTU1fQ==","QualityOfServiceLevel":1,"Retain":true,"UserProperties":null,"ContentType":null,"ResponseTopic":null,"PayloadFormatIndicator":null,"MessageExpiryInterval":null,"TopicAlias":null,"CorrelationData":null,"SubscriptionIdentifiers":null}] \ No newline at end of file