7 Commits

Author SHA1 Message Date
9af0ba1bb8 Best optimization so far 2020-11-21 19:42:14 +01:00
04c27560ea Added timeout processing 2020-11-21 19:18:29 +01:00
73157520ab Optimized chart update (did not achieve much) 2020-11-21 18:32:12 +01:00
f862e4b8da Added all charts (pre optimization) 2020-11-21 18:10:36 +01:00
f85346aea9 Added charts 2020-11-21 17:40:05 +01:00
1d438bc349 Modified dashboard 2020-11-21 10:38:52 +01:00
86999cd646 Moved service components to services folder 2020-11-21 10:22:11 +01:00
18 changed files with 716 additions and 136 deletions

View File

@ -52,7 +52,7 @@
<None Remove="ClientApp\src\components\auth\Auth.tsx" /> <None Remove="ClientApp\src\components\auth\Auth.tsx" />
<None Remove="ClientApp\src\components\auth\AuthClient.ts" /> <None Remove="ClientApp\src\components\auth\AuthClient.ts" />
<None Remove="ClientApp\src\components\auth\AuthService.ts" /> <None Remove="ClientApp\src\components\auth\AuthService.ts" />
<None Remove="ClientApp\src\components\dashboard\DashboardService.ts" /> <None Remove="ClientApp\src\components\dashboard\ServiceInfoService.ts" />
<None Remove="ClientApp\src\components\devices\DeviceService.ts" /> <None Remove="ClientApp\src\components\devices\DeviceService.ts" />
</ItemGroup> </ItemGroup>

View File

@ -1,13 +1,14 @@
import { Box, Grid, IconButton, Paper, Typography } from '@material-ui/core'; import React, { Component } from 'react';
import { blueGrey } from '@material-ui/core/colors';
import { AddBox, Refresh } from '@material-ui/icons/';
import { withStyles } from '@material-ui/styles'; import { withStyles } from '@material-ui/styles';
import { HubConnectionBuilder } from '@microsoft/signalr'; import Services from './services/Services';
import React, { Component } from 'react'; import { blueGrey } from '@material-ui/core/colors';
import AddNewDialog from './AddNewDialog'; import { Box, Grid, IconButton, Paper, Typography } from '@material-ui/core';
import DashboardService, { ServiceRequest } from './DashboardService'; import DonutChart from './charts/DonutChart';
import ServiceInfoComponent from './ServiceInfoComponent'; import HeatmapChart from './charts/HeatmapChart';
import ServiceInfoSkeleton from './ServiceInfoSkeleton'; import BarChart from './charts/BarChart';
import LineChart from './charts/LineChart';
import DevicesContext from '../../contexts/DevicesContext';
import C from '../../common/Constants';
const styles = theme => ({ const styles = theme => ({
root: { root: {
@ -15,142 +16,302 @@ const styles = theme => ({
padding: '64px', padding: '64px',
backgroundColor: theme.palette.primary.dark, backgroundColor: theme.palette.primary.dark,
}, },
paper: {
backgroundColor: blueGrey[50],
height: '60px',
},
typo: { typo: {
fontSize: theme.typography.pxToRem(20), fontSize: theme.typography.pxToRem(20),
fontWeight: theme.typography.fontWeightRegular, fontWeight: theme.typography.fontWeightRegular,
}, },
paper: {
backgroundColor: blueGrey[50],
padding: '16px',
}
}); });
const hub_url = "/hubs/services";
const notify_method_name = "NotifyUpdatedAsync";
class Dashboard extends Component { class Dashboard extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = { this.state = {
hubConnection: null, deviceSeries: [],
isDialogOpen: false, sensorSeries: [],
isLoading: false, heatmapSecondsSeries: [],
service: new DashboardService(), heatmapMinutesSeries: [],
services: [], barSeries: [],
serviceCount: [1, 2, 3], barCategories: [],
lineSeries: [],
};
this.updateSeries = this.updateSeries.bind(this);
this.updateDynamic = this.updateDynamic.bind(this);
this.performTask = this.performTask.bind(this);
} }
this.handleDevicesUpdated = this.handleDevicesUpdated.bind(this); static contextType = DevicesContext;
this.addDevice = this.addDevice.bind(this);
}
handleDevicesUpdated() {
this.setState({ isLoading: true });
this.state.service.getCount().then(result => {
const updatedCount = [];
for (var i = 0; i < result; i++) {
updatedCount.push(i);
}
this.setState({ serviceCount: updatedCount });
}).catch(ex => {
console.log(ex);
});
this.state.service.get().then(result => {
const updatedServices = [];
for (var s of result) {
updatedServices.push(s);
}
this.setState({ services: updatedServices });
}).catch(ex => {
console.log(ex);
}).finally(() => this.setState({ isLoading: false }));
}
componentDidMount() { componentDidMount() {
this.handleDevicesUpdated(); this.context.addHandler(C.update_all_method_name, this.updateSeries);
const newConnection = new HubConnectionBuilder() this.context.addHandler(C.update_method_name, this.updateSeries);
.withUrl(hub_url) this.updateSeries();
.withAutomaticReconnect() this.updateDynamic();
.build();
this.setState({ hubConnection: newConnection });
newConnection.start()
.then(_ => {
console.log('Services hub Connected!');
newConnection.on(notify_method_name, () => this.handleDevicesUpdated());
}).catch(e => console.log('Services hub Connection failed: ', e));
} }
componentWillUnmount() { componentWillUnmount() {
if (this.state.hubConnection != null) { this.context.removeHandler(C.update_all_method_name, this.updateSeries);
this.state.hubConnection.off(notify_method_name); this.context.removeHandler(C.update_method_name, this.updateSeries);
console.log('Services hub Disconnected!'); }
getItemsWithStatus(iterate, status) {
const items = [];
for (var d of iterate) {
if (d.status == status) {
items.push(d);
} }
} }
addDevice(name, url) { return items;
this.setState({ isDialogOpen: false }); }
let request = new ServiceRequest();
request.id = 0;
request.name = name;
request.uri = url;
this.state.service.post(request).catch(ex => { getDevicesWithStatus(status) {
console.log(ex); return this.getItemsWithStatus(this.context.devices, status);
}
getSensorsWithStatus(status) {
const sensors = [];
for (var d of this.context.devices) {
sensors.push(...d.sensors)
}
return this.getItemsWithStatus(sensors, status);
}
getDeviceSeries() {
var online = this.getDevicesWithStatus("Online").length;
var offline = this.getDevicesWithStatus("Offline").length;
var error = this.getDevicesWithStatus("Error").length;
return [online, offline, error]
}
getSensorSeries() {
var online = this.getSensorsWithStatus("Online").length;
var offline = this.getSensorsWithStatus("Offline").length;
var unknown = this.getSensorsWithStatus("Unknown").length;
return [online, offline, unknown]
}
updateSeries() {
this.setState({
deviceSeries: this.getDeviceSeries(),
sensorSeries: this.getSensorSeries()
}); });
} }
updateDynamic = () => {
const secondAgo = new Date();
secondAgo.setMilliseconds(0);
const minuteAgo = new Date( Date.now() - 1000 * 60 );
const hourAgo = new Date( Date.now() - 1000 * 60 * 60 );
const minuteDevicePoints = {};
const hourDevicePoints = {};
const barDevicePoints = {};
const linePoints = {};
for (var d of this.context.devices) {
minuteDevicePoints[d.id] = Array(60).fill(0);
hourDevicePoints[d.id] = Array(60).fill(0);
barDevicePoints[d.id] = Array(3).fill(0);
}
const processMethod = (items, index) => {
const p = items[index];
if (p.date > minuteAgo) {
var seconds = Math.floor((p.date.getTime() - minuteAgo.getTime()) / 1000);
var oldProb = minuteDevicePoints[p.deviceId][seconds];
if (oldProb < p.prob) {
minuteDevicePoints[p.deviceId][seconds] = p.prob;
}
}
if (p.date > hourAgo) {
var minutes = Math.floor((p.date.getTime() - hourAgo.getTime()) / (1000 * 60));
var oldProb = hourDevicePoints[p.deviceId][minutes];
if (oldProb < p.prob) {
hourDevicePoints[p.deviceId][minutes] = p.prob;
}
}
if (p.prob > 0.5 && p.prob <= 0.7) {
barDevicePoints[p.deviceId][0] += 1;
}
if (p.prob > 0.7 && p.prob <= 0.9) {
barDevicePoints[p.deviceId][1] += 1;
}
if (p.prob > 0.9) {
barDevicePoints[p.deviceId][2] += 1;
}
if (p.date < secondAgo) {
var shortDate = p.date.toUTCString();
var point = linePoints[shortDate];
if (point === undefined) {
linePoints[shortDate] = 1;
} else {
linePoints[shortDate] += 1;
}
}
}
const finishMethod = () => {
const minuteHeatmapSeries = [];
var i = 0;
for (var p in minuteDevicePoints) {
minuteHeatmapSeries.push({
name: "Device " + i,
data: minuteDevicePoints[p].map((value, index) => ({
x: new Date( Date.now() - (60 - index) * 1000 ).toLocaleTimeString('hu-HU'),
y: value
})),
});
i++;
};
const hourHeatmapSeries = [];
var i = 0;
for (var p in hourDevicePoints) {
hourHeatmapSeries.push({
name: "Device " + i,
data: hourDevicePoints[p].map((value, index) => ({
x: new Date( Date.now() - (60 - index) * 1000 * 60 ).toLocaleTimeString('hu-HU').substring(0, 5),
y: value
})),
});
i++;
};
const barSeries = [];
const getCount = column => {
var counts = [];
for (var p in barDevicePoints) {
counts.unshift(barDevicePoints[p][column]);
}
return counts;
};
barSeries.push({
name: "Prob > 0.5",
data: getCount(0),
});
barSeries.push({
name: "Prob > 0.7",
data: getCount(1),
});
barSeries.push({
name: "Prob > 0.9",
data: getCount(2),
});
const lineSeries = [{name: "message/sec", data: []}];
for (var m in linePoints) {
lineSeries[0].data.push({
x: new Date(m).getTime(),
y: linePoints[m],
})
}
const getBarCategories = () => {
const categories = [];
for (var i = this.context.devices.length - 1; i >= 0; i--) {
categories.push("Device " + i)
}
return categories;
}
this.setState({
heatmapSecondsSeries: minuteHeatmapSeries,
heatmapMinutesSeries: hourHeatmapSeries,
barSeries: barSeries,
barCategories: getBarCategories(),
lineSeries: lineSeries,
});
setTimeout(this.updateDynamic, 1000);
}
const processHeatmapItem = processMethod.bind(this);
const onFinished = finishMethod.bind(this)
this.performTask(this.context.heatmapPoints, Math.ceil(this.context.heatmapPoints.length / 100), 10,
processHeatmapItem, onFinished);
}
performTask(items, numToProcess, wait, processItem, onFinished) {
var pos = 0;
// This is run once for every numToProcess items.
function iteration() {
// Calculate last position.
var j = Math.min(pos + numToProcess, items.length);
// Start at current position and loop to last position.
for (var i = pos; i < j; i++) {
processItem(items, i);
}
// Increment current position.
pos += numToProcess;
// Only continue if there are more items to process.
if (pos < items.length)
setTimeout(iteration, wait); // Wait 10 ms to let the UI update.
else
onFinished();
}
iteration();
}
render() { render() {
const { classes } = this.props; const { classes } = this.props;
const Services = this.state.services.map((info, index) => (
<ServiceInfoComponent key={index} isAdmin={this.props.isAdmin} info={info} service={this.state.service} />
));
const Skeletons = this.state.serviceCount.map((i, index) => (
<ServiceInfoSkeleton key={index} />
));
return ( return (
<Box className={classes.root}> <Box className={classes.root}>
<Grid container spacing={3}> <Grid container spacing={3}>
<Grid item xs={8}> <Grid item xs={12}>
<Paper className={classes.paper} square> <Services isAdmin={this.props.isAdmin}/>
<Grid container
spacing={0}
direction="row"
justify="center"
alignItems="center">
<Grid item>
<Typography className={classes.typo}>Services</Typography>
</Grid>
<Grid item>
{this.props.isAdmin ?
<IconButton color="primary" onClick={() => this.setState({ isDialogOpen: true })}>
<AddBox fontSize="large" />
</IconButton>
: null
}
</Grid>
<Grid item>
<IconButton color="primary" onClick={this.handleDevicesUpdated}>
<Refresh fontSize="large" />
</IconButton>
</Grid>
<AddNewDialog open={this.state.isDialogOpen} handleClose={() => this.setState({ isDialogOpen: false })} handleAdd={this.addDevice}/>
</Grid> </Grid>
<Grid item xs={6}>
<Paper className={classes.paper}>
<DonutChart totalLabel="Devices" series={this.state.deviceSeries}/>
</Paper> </Paper>
{this.state.isLoading ? Skeletons : Services}
</Grid> </Grid>
<Grid item xs={4}> <Grid item xs={6}>
<Paper className={classes.paper} /> <Paper className={classes.paper}>
<DonutChart totalLabel="Sensors" series={this.state.sensorSeries}/>
</Paper>
</Grid> </Grid>
<Grid item xs={12}> <Grid item xs={12}>
<Paper className={classes.paper} /> <Paper className={classes.paper}>
<HeatmapChart label="Highest probability per second by devices" series={this.state.heatmapSecondsSeries}/>
</Paper>
</Grid>
<Grid item xs={12}>
<Paper className={classes.paper}>
<HeatmapChart label="Highest probability per minute by devices" series={this.state.heatmapMinutesSeries}/>
</Paper>
</Grid>
<Grid item xs={6}>
<Paper className={classes.paper}>
<BarChart label="# of messages by devices" series={this.state.barSeries} categories={this.state.barCategories}/>
</Paper>
</Grid>
<Grid item xs={6}>
<Paper className={classes.paper}>
<LineChart label="# of messages per second" series={this.state.lineSeries}/>
</Paper>
</Grid> </Grid>
</Grid> </Grid>
</Box> </Box>

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,92 @@
import React, { Component } from 'react';
import Chart from 'react-apexcharts';
import { blueGrey, green, red, orange, amber } from '@material-ui/core/colors';
export class BarChart extends Component {
constructor(props) {
super(props)
this.state = {
options: {},
};
}
componentDidUpdate(prevProps) {
if (prevProps.categories !== this.props.categories) {
this.setState({options: {
chart: {
stacked: true,
animations: {
enabled: true,
easing: 'linear',
speed: 250,
animateGradually: {
enabled: false,
},
dynamicAnimation: {
enabled: true,
speed: 250
}
},
},
plotOptions: {
bar: {
horizontal: true,
},
},
colors: [blueGrey[500], blueGrey[700], blueGrey[900]],
stroke: {
width: 1,
colors: ['#fff']
},
title: {
text: this.props.label,
style: {
fontSize: '22px',
fontWeight: 600,
fontFamily: 'Helvetica, Arial, sans-serif',
},
},
xaxis: {
categories: this.props.categories,
labels: {
formatter: function (val) {
return val;
}
}
},
yaxis: {
title: {
text: undefined
},
},
tooltip: {
y: {
formatter: function (val) {
return val;
}
}
},
fill: {
opacity: 1
},
legend: {
position: 'top',
}
}});
}
}
render() {
return (
<Chart
options={this.state.options}
series={this.props.series}
type="bar"
height={600}
/>
)
}
}
export default BarChart;

View File

@ -0,0 +1,67 @@
import React, { Component } from 'react';
import Chart from 'react-apexcharts';
import { blueGrey, green, red } from '@material-ui/core/colors';
export class DonutChart extends Component {
constructor(props) {
super(props);
this.state = {
options: {
legend: {
fontSize: '18px',
},
plotOptions: {
pie: {
startAngle: 0,
expandOnClick: false,
offsetX: 0,
offsetY: 0,
customScale: 1,
dataLabels: {
offset: 0,
minAngleToShowLabel: 10
},
donut: {
size: '65%',
background: 'transparent',
labels: {
show: true,
total: {
show: true,
showAlways: true,
label: props.totalLabel,
fontSize: '22px',
fontFamily: 'Helvetica, Arial, sans-serif',
fontWeight: 600,
color: '#373d3f',
formatter: function (w) {
return w.globals.seriesTotals.reduce((a, b) => {
return a + b
}, 0)
}
}
}
},
}
},
dataLabels: {
enabled: false
},
colors: [green[500], blueGrey[500], red[500]],
labels: ['Online', 'Offline', 'Error / Unknown']},
}
}
render() {
return (
<Chart
options={this.state.options}
series={this.props.series}
type="donut"/>
)
}
}
export default DonutChart;

View File

@ -0,0 +1,40 @@
import React, { Component } from 'react';
import Chart from 'react-apexcharts';
import { blueGrey, green, red } from '@material-ui/core/colors';
export class HeatmapChart extends Component {
constructor(props) {
super(props)
this.state = {
options: {
dataLabels: {
enabled: false
},
colors: [blueGrey[900]],
title: {
text: props.label,
style: {
fontSize: '22px',
fontWeight: 600,
fontFamily: 'Helvetica, Arial, sans-serif',
},
},
},
}
}
render() {
return (
<Chart
options={this.state.options}
series={this.props.series}
type="heatmap"
height={600}
/>
)
}
}
export default HeatmapChart

View File

@ -0,0 +1,74 @@
import React, { Component } from 'react';
import Chart from 'react-apexcharts';
import { blueGrey, green, red } from '@material-ui/core/colors';
export class LineChart extends Component {
constructor(props) {
super(props)
this.state = {
options: {
chart: {
animations: {
enabled: true,
easing: 'linear',
speed: 250,
animateGradually: {
enabled: false,
},
dynamicAnimation: {
enabled: true,
speed: 250
}
},
zoom: {
enabled: false
}
},
colors: [blueGrey[900]],
dataLabels: {
enabled: false
},
stroke: {
curve: 'straight'
},
title: {
text: this.props.label,
align: 'left',
style: {
fontSize: '22px',
fontWeight: 600,
fontFamily: 'Helvetica, Arial, sans-serif',
},
},
grid: {
row: {
colors: ['#f3f3f3', 'transparent'], // takes an array which will be repeated on columns
opacity: 0.5
},
},
xaxis: {
type: 'datetime',
labels: {
formatter: function (val) {
return new Date(val).toLocaleTimeString('hu-HU');
}
}
}
},
}
}
render() {
return (
<Chart
options={this.state.options}
series={this.props.series}
type="line"
height={600}
/>
)
}
}
export default LineChart

View File

@ -22,13 +22,13 @@ var __extends = (this && this.__extends) || (function () {
})(); })();
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
exports.ApiException = exports.HttpStatusCode = exports.ServiceRequest = exports.ServiceInfo = void 0; exports.ApiException = exports.HttpStatusCode = exports.ServiceRequest = exports.ServiceInfo = void 0;
var DashboardService = /** @class */ (function () { var ServiceInfoService = /** @class */ (function () {
function DashboardService(baseUrl, http) { function ServiceInfoService(baseUrl, http) {
this.jsonParseReviver = undefined; this.jsonParseReviver = undefined;
this.http = http ? http : window; this.http = http ? http : window;
this.baseUrl = baseUrl !== undefined && baseUrl !== null ? baseUrl : ""; this.baseUrl = baseUrl !== undefined && baseUrl !== null ? baseUrl : "";
} }
DashboardService.prototype.getCount = function () { ServiceInfoService.prototype.getCount = function () {
var _this = this; var _this = this;
var url_ = this.baseUrl + "/api/Services/count"; var url_ = this.baseUrl + "/api/Services/count";
url_ = url_.replace(/[?&]$/, ""); url_ = url_.replace(/[?&]$/, "");
@ -43,7 +43,7 @@ var DashboardService = /** @class */ (function () {
return _this.processGetCount(_response); return _this.processGetCount(_response);
}); });
}; };
DashboardService.prototype.processGetCount = function (response) { ServiceInfoService.prototype.processGetCount = function (response) {
var _this = this; var _this = this;
var status = response.status; var status = response.status;
var _headers = {}; var _headers = {};
@ -66,7 +66,7 @@ var DashboardService = /** @class */ (function () {
} }
return Promise.resolve(null); return Promise.resolve(null);
}; };
DashboardService.prototype.get = function () { ServiceInfoService.prototype.get = function () {
var _this = this; var _this = this;
var url_ = this.baseUrl + "/api/Services"; var url_ = this.baseUrl + "/api/Services";
url_ = url_.replace(/[?&]$/, ""); url_ = url_.replace(/[?&]$/, "");
@ -81,7 +81,7 @@ var DashboardService = /** @class */ (function () {
return _this.processGet(_response); return _this.processGet(_response);
}); });
}; };
DashboardService.prototype.processGet = function (response) { ServiceInfoService.prototype.processGet = function (response) {
var _this = this; var _this = this;
var status = response.status; var status = response.status;
var _headers = {}; var _headers = {};
@ -110,7 +110,7 @@ var DashboardService = /** @class */ (function () {
} }
return Promise.resolve(null); return Promise.resolve(null);
}; };
DashboardService.prototype.post = function (request) { ServiceInfoService.prototype.post = function (request) {
var _this = this; var _this = this;
var url_ = this.baseUrl + "/api/Services"; var url_ = this.baseUrl + "/api/Services";
url_ = url_.replace(/[?&]$/, ""); url_ = url_.replace(/[?&]$/, "");
@ -128,7 +128,7 @@ var DashboardService = /** @class */ (function () {
return _this.processPost(_response); return _this.processPost(_response);
}); });
}; };
DashboardService.prototype.processPost = function (response) { ServiceInfoService.prototype.processPost = function (response) {
var _this = this; var _this = this;
var status = response.status; var status = response.status;
var _headers = {}; var _headers = {};
@ -151,7 +151,7 @@ var DashboardService = /** @class */ (function () {
} }
return Promise.resolve(null); return Promise.resolve(null);
}; };
DashboardService.prototype.put = function (request) { ServiceInfoService.prototype.put = function (request) {
var _this = this; var _this = this;
var url_ = this.baseUrl + "/api/Services"; var url_ = this.baseUrl + "/api/Services";
url_ = url_.replace(/[?&]$/, ""); url_ = url_.replace(/[?&]$/, "");
@ -168,7 +168,7 @@ var DashboardService = /** @class */ (function () {
return _this.processPut(_response); return _this.processPut(_response);
}); });
}; };
DashboardService.prototype.processPut = function (response) { ServiceInfoService.prototype.processPut = function (response) {
var status = response.status; var status = response.status;
var _headers = {}; var _headers = {};
if (response.headers && response.headers.forEach) { if (response.headers && response.headers.forEach) {
@ -187,7 +187,7 @@ var DashboardService = /** @class */ (function () {
} }
return Promise.resolve(null); return Promise.resolve(null);
}; };
DashboardService.prototype.delete = function (id) { ServiceInfoService.prototype.delete = function (id) {
var _this = this; var _this = this;
var url_ = this.baseUrl + "/api/Services/{id}"; var url_ = this.baseUrl + "/api/Services/{id}";
if (id === undefined || id === null) if (id === undefined || id === null)
@ -204,7 +204,7 @@ var DashboardService = /** @class */ (function () {
return _this.processDelete(_response); return _this.processDelete(_response);
}); });
}; };
DashboardService.prototype.processDelete = function (response) { ServiceInfoService.prototype.processDelete = function (response) {
var status = response.status; var status = response.status;
var _headers = {}; var _headers = {};
if (response.headers && response.headers.forEach) { if (response.headers && response.headers.forEach) {
@ -223,9 +223,9 @@ var DashboardService = /** @class */ (function () {
} }
return Promise.resolve(null); return Promise.resolve(null);
}; };
return DashboardService; return ServiceInfoService;
}()); }());
exports.default = DashboardService; exports.default = ServiceInfoService;
var ServiceInfo = /** @class */ (function () { var ServiceInfo = /** @class */ (function () {
function ServiceInfo(data) { function ServiceInfo(data) {
if (data) { if (data) {
@ -383,4 +383,4 @@ function throwException(message, status, response, headers, result) {
else else
throw new ApiException(message, status, response, headers, null); throw new ApiException(message, status, response, headers, null);
} }
//# sourceMappingURL=DashboardService.js.map //# sourceMappingURL=SystemInfoService.js.map

File diff suppressed because one or more lines are too long

View File

@ -7,7 +7,7 @@
//---------------------- //----------------------
// ReSharper disable InconsistentNaming // ReSharper disable InconsistentNaming
export default class DashboardService { export default class ServiceInfoService {
private http: { fetch(url: RequestInfo, init?: RequestInit): Promise<Response> }; private http: { fetch(url: RequestInfo, init?: RequestInit): Promise<Response> };
private baseUrl: string; private baseUrl: string;
protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined; protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined;

View File

@ -0,0 +1,146 @@
import { Grid, IconButton, Paper, Typography } from '@material-ui/core';
import { blueGrey } from '@material-ui/core/colors';
import { AddBox, Refresh } from '@material-ui/icons/';
import { withStyles } from '@material-ui/styles';
import { HubConnectionBuilder } from '@microsoft/signalr';
import React, { Component } from 'react';
import AddNewDialog from './AddNewDialog';
import ServiceInfoService, { ServiceRequest } from './ServiceInfoService';
import ServiceInfoComponent from './ServiceInfoComponent';
import ServiceInfoSkeleton from './ServiceInfoSkeleton';
const styles = theme => ({
typo: {
fontSize: theme.typography.pxToRem(20),
fontWeight: theme.typography.fontWeightRegular,
},
paper: {
backgroundColor: blueGrey[50],
height: '60px',
}
});
const hub_url = "/hubs/services";
const notify_method_name = "NotifyUpdatedAsync";
class Services extends Component {
constructor(props) {
super(props);
this.state = {
hubConnection: null,
isDialogOpen: false,
isLoading: false,
service: new ServiceInfoService(),
services: [],
serviceCount: [1, 2, 3],
}
this.handleDevicesUpdated = this.handleDevicesUpdated.bind(this);
this.addDevice = this.addDevice.bind(this);
}
handleDevicesUpdated() {
this.setState({ isLoading: true });
this.state.service.getCount().then(result => {
const updatedCount = [];
for (var i = 0; i < result; i++) {
updatedCount.push(i);
}
this.setState({ serviceCount: updatedCount });
}).catch(ex => {
console.log(ex);
});
this.state.service.get().then(result => {
const updatedServices = [];
for (var s of result) {
updatedServices.push(s);
}
this.setState({ services: updatedServices });
}).catch(ex => {
console.log(ex);
}).finally(() => this.setState({ isLoading: false }));
}
componentDidMount() {
this.handleDevicesUpdated();
const newConnection = new HubConnectionBuilder()
.withUrl(hub_url)
.withAutomaticReconnect()
.build();
this.setState({ hubConnection: newConnection });
newConnection.start()
.then(_ => {
console.log('Services hub Connected!');
newConnection.on(notify_method_name, () => this.handleDevicesUpdated());
}).catch(e => console.log('Services hub Connection failed: ', e));
}
componentWillUnmount() {
if (this.state.hubConnection != null) {
this.state.hubConnection.off(notify_method_name);
console.log('Services hub Disconnected!');
}
}
addDevice(name, url) {
this.setState({ isDialogOpen: false });
let request = new ServiceRequest();
request.id = 0;
request.name = name;
request.uri = url;
this.state.service.post(request).catch(ex => {
console.log(ex);
});
}
render() {
const { classes } = this.props;
const ServiceComponents = this.state.services.map((info, index) => (
<ServiceInfoComponent key={index} isAdmin={this.props.isAdmin} info={info} service={this.state.service} />
));
const Skeletons = this.state.serviceCount.map((i, index) => (
<ServiceInfoSkeleton key={index} />
));
return (
<React.Fragment>
<Paper className={classes.paper} square>
<Grid container
spacing={0}
direction="row"
justify="center"
alignItems="center">
<Grid item>
<Typography className={classes.typo}>Services</Typography>
</Grid>
<Grid item>
{this.props.isAdmin ?
<IconButton color="primary" onClick={() => this.setState({ isDialogOpen: true })}>
<AddBox fontSize="large" />
</IconButton>
: null
}
</Grid>
<Grid item>
<IconButton color="primary" onClick={this.handleDevicesUpdated}>
<Refresh fontSize="large" />
</IconButton>
</Grid>
<AddNewDialog open={this.state.isDialogOpen} handleClose={() => this.setState({ isDialogOpen: false })} handleAdd={this.addDevice} />
</Grid>
</Paper>
{this.state.isLoading ? Skeletons : ServiceComponents}
</React.Fragment>
);
}
}
export default withStyles(styles)(Services);

View File

@ -63,8 +63,8 @@ class DeviceComponent extends Component {
if (status == "Online") { if (status == "Online") {
return { color: green[600] }; return { color: green[600] };
} else if (status == "Offline") { } else if (status == "Offline") {
return { color: orange[900] }; return { color: blueGrey[500] };
} else /* if (device.status == "unknown") */ { } else /* if (device.status == "Unknown" || device.status == "Error") */ {
return { color: red[800] }; return { color: red[800] };
} }
} }

View File

@ -16,9 +16,9 @@ class DeviceMarker extends Component {
getColor() { getColor() {
const { device } = this.props; const { device } = this.props;
if (device.status == "Online") { if (device.status === "Online") {
return { color: blue[800] }; return { color: blue[800] };
} else if (device.status == "Offline") { } else if (device.status === "Offline") {
return { color: yellow[800] }; return { color: yellow[800] };
} else /* if (device.status == "unknown") */ { } else /* if (device.status == "unknown") */ {
return { color: red[800] }; return { color: red[800] };

View File

@ -60,6 +60,7 @@ export default class DevicesContextProvider extends Component {
} }
service.getall().then(result => { service.getall().then(result => {
this.setState({ devices: result }); this.setState({ devices: result });
this.invokeHandlers(C.update_all_method_name, null);
}).catch(ex => { }).catch(ex => {
console.log(ex); console.log(ex);
}); });
@ -101,7 +102,7 @@ export default class DevicesContextProvider extends Component {
newConnection.on(C.probability_method_name, (id, date, prob) => { newConnection.on(C.probability_method_name, (id, date, prob) => {
//console.log(method_name + " recieved: [id: " + id + ", date: " + date + ", prob: " + prob + "]"); //console.log(method_name + " recieved: [id: " + id + ", date: " + date + ", prob: " + prob + "]");
var device = this.state.devices.filter(function (x) { return x.id === id })[0] var device = this.state.devices.filter(function (x) { return x.id === id })[0]
var newPoint = { lat: device.coordinates.latitude, lng: device.coordinates.longitude, prob: prob, date: date }; var newPoint = { deviceId: device.id, lat: device.coordinates.latitude, lng: device.coordinates.longitude, prob: prob, date: new Date(date) };
this.setState({ this.setState({
heatmapPoints: [...this.state.heatmapPoints, newPoint] heatmapPoints: [...this.state.heatmapPoints, newPoint]
}); });
@ -111,7 +112,6 @@ export default class DevicesContextProvider extends Component {
newConnection.on(C.update_all_method_name, () => { newConnection.on(C.update_all_method_name, () => {
this.updateAllDevicesInternal(service); this.updateAllDevicesInternal(service);
this.invokeHandlers(C.update_all_method_name, null);
}); });
newConnection.on(C.update_method_name, (id) => this.updateDeviceInternal(id, service)); newConnection.on(C.update_method_name, (id) => this.updateDeviceInternal(id, service));