data)[property];
+ }
+ }
+ }
+
+ init(_data?: any) {
+ if (_data) {
+ this.id = _data["id"];
+ this.name = _data["name"];
+ this.uri = _data["uri"];
+ }
+ }
+
+ static fromJS(data: any): ServiceRequest {
+ data = typeof data === 'object' ? data : {};
+ let result = new ServiceRequest();
+ result.init(data);
+ return result;
+ }
+
+ toJSON(data?: any) {
+ data = typeof data === 'object' ? data : {};
+ data["id"] = this.id;
+ data["name"] = this.name;
+ data["uri"] = this.uri;
+ return data;
+ }
+}
+
+export interface IServiceRequest {
+ id: number;
+ name?: string | undefined;
+ uri?: string | undefined;
+}
+
+export enum HttpStatusCode {
+ Continue = "Continue",
+ SwitchingProtocols = "SwitchingProtocols",
+ Processing = "Processing",
+ EarlyHints = "EarlyHints",
+ OK = "OK",
+ Created = "Created",
+ Accepted = "Accepted",
+ NonAuthoritativeInformation = "NonAuthoritativeInformation",
+ NoContent = "NoContent",
+ ResetContent = "ResetContent",
+ PartialContent = "PartialContent",
+ MultiStatus = "MultiStatus",
+ AlreadyReported = "AlreadyReported",
+ IMUsed = "IMUsed",
+ MultipleChoices = "Ambiguous",
+ Ambiguous = "Ambiguous",
+ MovedPermanently = "Moved",
+ Moved = "Moved",
+ Found = "Redirect",
+ Redirect = "Redirect",
+ SeeOther = "RedirectMethod",
+ RedirectMethod = "RedirectMethod",
+ NotModified = "NotModified",
+ UseProxy = "UseProxy",
+ Unused = "Unused",
+ TemporaryRedirect = "TemporaryRedirect",
+ RedirectKeepVerb = "TemporaryRedirect",
+ PermanentRedirect = "PermanentRedirect",
+ BadRequest = "BadRequest",
+ Unauthorized = "Unauthorized",
+ PaymentRequired = "PaymentRequired",
+ Forbidden = "Forbidden",
+ NotFound = "NotFound",
+ MethodNotAllowed = "MethodNotAllowed",
+ NotAcceptable = "NotAcceptable",
+ ProxyAuthenticationRequired = "ProxyAuthenticationRequired",
+ RequestTimeout = "RequestTimeout",
+ Conflict = "Conflict",
+ Gone = "Gone",
+ LengthRequired = "LengthRequired",
+ PreconditionFailed = "PreconditionFailed",
+ RequestEntityTooLarge = "RequestEntityTooLarge",
+ RequestUriTooLong = "RequestUriTooLong",
+ UnsupportedMediaType = "UnsupportedMediaType",
+ RequestedRangeNotSatisfiable = "RequestedRangeNotSatisfiable",
+ ExpectationFailed = "ExpectationFailed",
+ MisdirectedRequest = "MisdirectedRequest",
+ UnprocessableEntity = "UnprocessableEntity",
+ Locked = "Locked",
+ FailedDependency = "FailedDependency",
+ UpgradeRequired = "UpgradeRequired",
+ PreconditionRequired = "PreconditionRequired",
+ TooManyRequests = "TooManyRequests",
+ RequestHeaderFieldsTooLarge = "RequestHeaderFieldsTooLarge",
+ UnavailableForLegalReasons = "UnavailableForLegalReasons",
+ InternalServerError = "InternalServerError",
+ NotImplemented = "NotImplemented",
+ BadGateway = "BadGateway",
+ ServiceUnavailable = "ServiceUnavailable",
+ GatewayTimeout = "GatewayTimeout",
+ HttpVersionNotSupported = "HttpVersionNotSupported",
+ VariantAlsoNegotiates = "VariantAlsoNegotiates",
+ InsufficientStorage = "InsufficientStorage",
+ LoopDetected = "LoopDetected",
+ NotExtended = "NotExtended",
+ NetworkAuthenticationRequired = "NetworkAuthenticationRequired",
+}
+
+export class ApiException extends Error {
+ message: string;
+ status: number;
+ response: string;
+ headers: { [key: string]: any; };
+ result: any;
+
+ constructor(message: string, status: number, response: string, headers: { [key: string]: any; }, result: any) {
+ super();
+
+ this.message = message;
+ this.status = status;
+ this.response = response;
+ this.headers = headers;
+ this.result = result;
+ }
+
+ protected isApiException = true;
+
+ static isApiException(obj: any): obj is ApiException {
+ return obj.isApiException === true;
+ }
+}
+
+function throwException(message: string, status: number, response: string, headers: { [key: string]: any; }, result?: any): any {
+ if (result !== null && result !== undefined)
+ throw result;
+ else
+ throw new ApiException(message, status, response, headers, null);
+}
\ No newline at end of file
diff --git a/Birdmap.API/ClientApp/src/components/dashboard/DeleteDialog.jsx b/Birdmap.API/ClientApp/src/components/dashboard/DeleteDialog.jsx
new file mode 100644
index 0000000..bb523b7
--- /dev/null
+++ b/Birdmap.API/ClientApp/src/components/dashboard/DeleteDialog.jsx
@@ -0,0 +1,35 @@
+import Button from '@material-ui/core/Button';
+import Dialog from '@material-ui/core/Dialog';
+import DialogActions from '@material-ui/core/DialogActions';
+import DialogContent from '@material-ui/core/DialogContent';
+import DialogContentText from '@material-ui/core/DialogContentText';
+import DialogTitle from '@material-ui/core/DialogTitle';
+import React from 'react';
+
+export default function DeleteDialog(props) {
+ return (
+
+
+
+ );
+}
diff --git a/Birdmap.API/ClientApp/src/components/dashboard/ServiceInfoComponent.jsx b/Birdmap.API/ClientApp/src/components/dashboard/ServiceInfoComponent.jsx
new file mode 100644
index 0000000..0f68222
--- /dev/null
+++ b/Birdmap.API/ClientApp/src/components/dashboard/ServiceInfoComponent.jsx
@@ -0,0 +1,234 @@
+import { Box, FormControlLabel, Grid, IconButton, Paper, TextField, Typography } from '@material-ui/core';
+import Accordion from '@material-ui/core/Accordion';
+import AccordionDetails from '@material-ui/core/AccordionDetails';
+import AccordionSummary from '@material-ui/core/AccordionSummary';
+import { blueGrey, green, red } from '@material-ui/core/colors';
+import { CancelRounded, CheckCircleRounded, Delete, Edit } from '@material-ui/icons/';
+import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
+import { withStyles } from '@material-ui/styles';
+import React, { Component } from 'react';
+import DeleteDialog from './DeleteDialog';
+
+const styles = theme => ({
+ root: {
+ flexGrow: 1,
+ padding: '64px',
+ backgroundColor: theme.palette.primary.dark,
+ },
+ acc_summary: {
+ backgroundColor: blueGrey[50],
+ padding: theme.spacing(2),
+ textAlign: 'center',
+ height: '75px',
+ },
+ acc_details: {
+ backgroundColor: blueGrey[100],
+ },
+ grid_typo: {
+ fontSize: theme.typography.pxToRem(20),
+ fontWeight: theme.typography.fontWeightRegular,
+ },
+ grid_typo_2: {
+ marginLeft: '5px',
+ fontSize: theme.typography.pxToRem(15),
+ fontWeight: theme.typography.fontWeightRegular,
+ color: theme.palette.text.secondary,
+ },
+ typo: {
+ fontSize: theme.typography.pxToRem(20),
+ fontWeight: theme.typography.fontWeightRegular,
+ },
+ icon_box: {
+ marginLeft: '30px',
+ },
+ paper: {
+ backgroundColor: blueGrey[50],
+ padding: theme.spacing(2),
+ textAlign: 'center',
+ height: '75px',
+ }
+});
+
+class ServiceInfoComponent extends Component {
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ isDialogOpen: false,
+ isEditing: false,
+ name: "",
+ url: "",
+ }
+
+ this.handleDialogClose = this.handleDialogClose.bind(this);
+ this.onNameChange = this.onNameChange.bind(this);
+ this.onUrlChange = this.onUrlChange.bind(this);
+ this.handleSaveCancel = this.handleSaveCancel.bind(this);
+ }
+
+ componentDidMount() {
+ this.setState({ name: this.props.info.service.name, url: this.props.info.service.uri });
+
+ if (this.props.isEditing !== undefined) {
+ this.setState({ isEditing: this.props.isEditing });
+ }
+ }
+
+ onNameChange(event) {
+ this.setState({ name: event.target.value });
+ }
+
+ onUrlChange(event) {
+ this.setState({ url: event.target.value });
+ }
+
+ getColor(status) {
+ if (status === "OK")
+ return { color: green[600] };
+ else
+ return { color: red[600] };
+ }
+
+ handleDialogClose(result) {
+ this.setState({ isDialogOpen: false });
+ if (result === true) {
+ this.props.service.delete(this.props.info.service.id);
+ }
+ }
+
+ handleEditCancel(value) {
+ this.setState({ isEditing: value });
+ }
+
+ handleSaveCancel() {
+ let request = {
+ ...this.props.info.service
+ };
+
+ request.name = this.state.name;
+ request.uri = this.state.url;
+
+ if (request.id > 0) {
+ this.props.service.put(request).catch(ex => {
+ console.log(ex);
+ });
+ }
+ else {
+ this.props.service.post(request).catch(ex => {
+ console.log(ex);
+ });
+ }
+
+ this.setState({ isEditing: false });
+ }
+
+ renderSaveCancel() {
+ return (
+
+
+
+
+ this.setState({ isEditing: false })}>
+
+
+
+ );
+ }
+
+ renderButtons() {
+ const renderEditDelete = () => {
+ return (
+
+
+ this.handleEditCancel(true)}>
+
+
+ this.setState({ isDialogOpen: true })}>
+
+
+
+ );
+ }
+
+ const { classes } = this.props;
+ return (
+
+ {this.props.isAdmin && this.props.info.service.name !== "Mqtt Client Service" ? renderEditDelete() : null}
+
+ );
+ }
+
+ render() {
+ const { classes } = this.props;
+
+ const renderAccordion = () => {
+ return (
+
+ }
+ aria-controls={"device-panel-/" + this.props.info.service.name}
+ id={"device-panel-/" + this.props.info.service.name}>
+
+
+ {this.props.info.service.name}
+
+
+ {this.props.info.service.uri}
+
+
+
+
+ event.stopPropagation()}
+ onFocus={(event) => event.stopPropagation()}
+ control={this.renderButtons()} />
+
+
+ Status: {this.props.info.statusCode}
+
+
+
+
+
+
+ {this.props.info.response}
+
+
+ );
+ };
+
+ const renderTextFields = () => {
+ return (
+
+
+
+
+
+
+
+
+
+ {this.renderSaveCancel()}
+
+
+
+ );
+ };
+
+ return this.state.isEditing ? renderTextFields() : renderAccordion();
+ }
+}
+
+export default withStyles(styles)(ServiceInfoComponent);
\ No newline at end of file
diff --git a/Birdmap.API/ClientApp/src/components/dashboard/ServiceInfoSkeleton.jsx b/Birdmap.API/ClientApp/src/components/dashboard/ServiceInfoSkeleton.jsx
new file mode 100644
index 0000000..17d6a65
--- /dev/null
+++ b/Birdmap.API/ClientApp/src/components/dashboard/ServiceInfoSkeleton.jsx
@@ -0,0 +1,88 @@
+import { Grid, Typography } from '@material-ui/core';
+import Accordion from '@material-ui/core/Accordion';
+import AccordionDetails from '@material-ui/core/AccordionDetails';
+import AccordionSummary from '@material-ui/core/AccordionSummary';
+import { blueGrey } from '@material-ui/core/colors';
+import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
+import { Skeleton } from '@material-ui/lab';
+import { withStyles } from '@material-ui/styles';
+import React, { Component } from 'react';
+
+const styles = theme => ({
+ acc_summary: {
+ backgroundColor: blueGrey[50],
+ padding: theme.spacing(2),
+ textAlign: 'center',
+ height: '75px',
+ },
+ acc_details: {
+ backgroundColor: blueGrey[100],
+ },
+ grid_typo: {
+ fontSize: theme.typography.pxToRem(20),
+ fontWeight: theme.typography.fontWeightRegular,
+ },
+ grid_typo_2: {
+ marginLeft: '5px',
+ fontSize: theme.typography.pxToRem(15),
+ fontWeight: theme.typography.fontWeightRegular,
+ color: theme.palette.text.secondary,
+ },
+});
+
+class ServiceInfoSkeleton extends Component {
+
+ render() {
+ const { classes } = this.props;
+
+ return (
+
+ }>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
+export default withStyles(styles)(ServiceInfoSkeleton);
\ No newline at end of file
diff --git a/Birdmap.API/ClientApp/src/components/devices/DeviceComponent.jsx b/Birdmap.API/ClientApp/src/components/devices/DeviceComponent.jsx
index 7415a93..6e73d8d 100644
--- a/Birdmap.API/ClientApp/src/components/devices/DeviceComponent.jsx
+++ b/Birdmap.API/ClientApp/src/components/devices/DeviceComponent.jsx
@@ -1,14 +1,14 @@
-import React, { Component } from 'react';
+import { Box, FormControlLabel, Grid, IconButton, Typography } from '@material-ui/core';
import Accordion from '@material-ui/core/Accordion';
-import { blue, blueGrey, green, orange, red, yellow } from '@material-ui/core/colors';
-import AccordionSummary from '@material-ui/core/AccordionSummary';
import AccordionDetails from '@material-ui/core/AccordionDetails';
-import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
-import { Grid, Typography, Paper, IconButton, Box, FormControlLabel } from '@material-ui/core';
-import { withStyles } from '@material-ui/styles';
-import { withRouter } from "react-router";
+import AccordionSummary from '@material-ui/core/AccordionSummary';
+import { blueGrey, green, orange, red } from '@material-ui/core/colors';
import { Power, PowerOff, Refresh } from '@material-ui/icons/';
-import DeviceService from '../../common/DeviceService'
+import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
+import { withStyles } from '@material-ui/styles';
+import React, { Component } from 'react';
+import { withRouter } from "react-router";
+import DeviceService from '../../common/DeviceService';
import DevicesContext from '../../contexts/DevicesContext';
const styles = theme => ({
@@ -31,6 +31,7 @@ const styles = theme => ({
grid_item: {
width: '100%',
marginLeft: '5px',
+ marginRight: '30px',
},
grid_item_typo: {
fontSize: theme.typography.pxToRem(15),
diff --git a/Birdmap.API/ClientApp/src/components/devices/Devices.jsx b/Birdmap.API/ClientApp/src/components/devices/Devices.jsx
index 5e3d2d4..313295a 100644
--- a/Birdmap.API/ClientApp/src/components/devices/Devices.jsx
+++ b/Birdmap.API/ClientApp/src/components/devices/Devices.jsx
@@ -1,11 +1,11 @@
-import { Box, Paper, Typography, IconButton, Grid } from '@material-ui/core';
-import { blue, blueGrey, green, orange, red, yellow } from '@material-ui/core/colors';
+import { Box, Grid, IconButton, Paper, Typography } from '@material-ui/core';
+import { blueGrey } from '@material-ui/core/colors';
+import { Power, PowerOff, Refresh } from '@material-ui/icons/';
import { withStyles } from '@material-ui/styles';
import React from 'react';
import DeviceService from '../../common/DeviceService';
import DevicesContext from '../../contexts/DevicesContext';
import DeviceComponent from './DeviceComponent';
-import { Power, PowerOff, Refresh } from '@material-ui/icons/';
const styles = theme => ({
root: {
@@ -15,7 +15,6 @@ const styles = theme => ({
paper: {
backgroundColor: blueGrey[50],
height: '60px',
- margin: 'auto',
},
typo: {
fontSize: theme.typography.pxToRem(20),
@@ -69,15 +68,15 @@ class Devices extends React.Component {
All Devices
- {this.renderButtons()}
+ {this.renderButtons()}
diff --git a/Birdmap.API/ClientApp/src/components/heatmap/DeviceMarker.jsx b/Birdmap.API/ClientApp/src/components/heatmap/DeviceMarker.jsx
index 80823c3..fc4f5b3 100644
--- a/Birdmap.API/ClientApp/src/components/heatmap/DeviceMarker.jsx
+++ b/Birdmap.API/ClientApp/src/components/heatmap/DeviceMarker.jsx
@@ -1,11 +1,9 @@
-import RadioButtonCheckedIcon from '@material-ui/icons/RadioButtonChecked';
-import PlayCircleFilledIcon from '@material-ui/icons/PlayCircleFilled';
-import { shadows } from '@material-ui/system';
-import { Box, Popover, Typography, Tooltip, Grid } from '@material-ui/core';
+import { Box, Tooltip } from '@material-ui/core';
import { blue, red, yellow } from '@material-ui/core/colors';
-import React, { Component } from 'react';
-import { useHistory, withRouter } from 'react-router-dom';
+import RadioButtonCheckedIcon from '@material-ui/icons/RadioButtonChecked';
import { makeStyles } from '@material-ui/styles';
+import React, { Component } from 'react';
+import { withRouter } from 'react-router-dom';
class DeviceMarker extends Component {
constructor(props) {
diff --git a/Birdmap.API/ClientApp/src/components/heatmap/Heatmap.jsx b/Birdmap.API/ClientApp/src/components/heatmap/Heatmap.jsx
index a200483..11a6cca 100644
--- a/Birdmap.API/ClientApp/src/components/heatmap/Heatmap.jsx
+++ b/Birdmap.API/ClientApp/src/components/heatmap/Heatmap.jsx
@@ -1,9 +1,9 @@
/*global google*/
import GoogleMapReact from 'google-map-react';
import React, { Component } from 'react';
-import DeviceMarker from './DeviceMarker'
-import C from '../../common/Constants'
+import C from '../../common/Constants';
import DevicesContext from '../../contexts/DevicesContext';
+import DeviceMarker from './DeviceMarker';
const lat_offset = 0.000038;
const lng_offset = -0.000058;
diff --git a/Birdmap.API/ClientApp/src/contexts/DevicesContextProvider.js b/Birdmap.API/ClientApp/src/contexts/DevicesContextProvider.js
index 21e1185..9680b77 100644
--- a/Birdmap.API/ClientApp/src/contexts/DevicesContextProvider.js
+++ b/Birdmap.API/ClientApp/src/contexts/DevicesContextProvider.js
@@ -96,7 +96,7 @@ export default class DevicesContextProvider extends Component {
newConnection.start()
.then(_ => {
- console.log('Hub Connected!');
+ console.log('Devices hub Connected!');
newConnection.on(C.probability_method_name, (id, date, prob) => {
//console.log(method_name + " recieved: [id: " + id + ", date: " + date + ", prob: " + prob + "]");
@@ -115,7 +115,7 @@ export default class DevicesContextProvider extends Component {
});
newConnection.on(C.update_method_name, (id) => this.updateDeviceInternal(id, service));
- }).catch(e => console.log('Hub Connection failed: ', e));
+ }).catch(e => console.log('Devices hub Connection failed: ', e));
}
componentWillUnmount() {
@@ -123,7 +123,7 @@ export default class DevicesContextProvider extends Component {
this.state.hubConnection.off(C.probability_method_name);
this.state.hubConnection.off(C.update_all_method_name);
this.state.hubConnection.off(C.update_method_name);
- console.log('Hub Disconnected!');
+ console.log('Devices hub Disconnected!');
}
}
diff --git a/Birdmap.API/Controllers/ServicesController.cs b/Birdmap.API/Controllers/ServicesController.cs
index 869f84c..79aa554 100644
--- a/Birdmap.API/Controllers/ServicesController.cs
+++ b/Birdmap.API/Controllers/ServicesController.cs
@@ -1,14 +1,19 @@
using AutoMapper;
using Birdmap.API.DTOs;
+using Birdmap.API.Services;
+using Birdmap.API.Services.Hubs;
+using Birdmap.API.Services.Mqtt;
using Birdmap.BLL.Interfaces;
using Birdmap.DAL.Entities;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
@@ -21,21 +26,34 @@ namespace Birdmap.API.Controllers
{
private readonly IServiceService _service;
private readonly IMapper _mapper;
+ private readonly IMqttClientService _mqttClientService;
+ private readonly IHubContext _hubContext;
private readonly ILogger _logger;
- public ServicesController(IServiceService service, IMapper mapper, ILogger logger)
+ public ServicesController(IServiceService service, IMapper mapper, MqttClientServiceProvider mqttClientProvider,
+ IHubContext hubContext, ILogger logger)
{
_service = service;
_mapper = mapper;
+ _mqttClientService = mqttClientProvider.MqttClientService;
+ _hubContext = hubContext;
_logger = logger;
}
+ [HttpGet("count"), ProducesResponseType(StatusCodes.Status200OK)]
+ public async Task> GetCountAsync()
+ {
+ _logger.LogInformation($"Getting service count from db...");
+
+ return await _service.GetServiceCountAsync() + 1;
+ }
+
[HttpGet, ProducesResponseType(StatusCodes.Status200OK)]
public async Task>> GetAsync()
{
_logger.LogInformation($"Getting all services from db...");
var serviceInfos = (await _service.GetAllServicesAsync())
- .Select(s => new ServiceInfo { Service = _mapper.Map(s) });
+ .Select(s => new ServiceInfo { Service = _mapper.Map(s) }).ToList();
var client = new HttpClient();
foreach (var si in serviceInfos)
@@ -50,11 +68,23 @@ namespace Birdmap.API.Controllers
catch (Exception ex)
{
_logger.LogWarning($"Requesting service [{si.Service.Name}] faulted.");
- si.StatusCode = System.Net.HttpStatusCode.ServiceUnavailable;
+ si.StatusCode = HttpStatusCode.ServiceUnavailable;
si.Response = ex.ToString();
}
}
+ serviceInfos.Add(new()
+ {
+ Service = new()
+ {
+ Id = 0,
+ Name = "Mqtt Client Service",
+ Uri = "localhost",
+ },
+ Response = $"IsConnected: {_mqttClientService.IsConnected}",
+ StatusCode = _mqttClientService.IsConnected ? HttpStatusCode.OK : HttpStatusCode.ServiceUnavailable,
+ });
+
return serviceInfos.ToList();
}
@@ -67,6 +97,7 @@ namespace Birdmap.API.Controllers
_mapper.Map(request));
_logger.LogInformation($"Created service [{created.Id}].");
+ await _hubContext.Clients.All.NotifyUpdatedAsync();
return CreatedAtAction(
nameof(GetAsync),
@@ -82,6 +113,7 @@ namespace Birdmap.API.Controllers
service.IsFromConfig = false;
await _service.UpdateServiceAsync(service);
+ await _hubContext.Clients.All.NotifyUpdatedAsync();
return NoContent();
}
@@ -93,6 +125,7 @@ namespace Birdmap.API.Controllers
_logger.LogInformation($"Deleting service [{id}]...");
await _service.DeleteServiceAsync(id);
+ await _hubContext.Clients.All.NotifyUpdatedAsync();
return NoContent();
}
diff --git a/Birdmap.API/Services/Hubs/DevicesHub.cs b/Birdmap.API/Services/Hubs/DevicesHub.cs
index 887b548..9938b42 100644
--- a/Birdmap.API/Services/Hubs/DevicesHub.cs
+++ b/Birdmap.API/Services/Hubs/DevicesHub.cs
@@ -16,31 +16,16 @@ namespace Birdmap.API.Services.Hubs
public override Task OnConnectedAsync()
{
- _logger.LogInformation("Hub Client connected.");
+ _logger.LogInformation("Devices Hub Client connected.");
return base.OnConnectedAsync();
}
public override Task OnDisconnectedAsync(Exception exception)
{
- _logger.LogInformation("Hub Client disconnected.");
+ _logger.LogInformation("Devices Hub Client disconnected.");
return base.OnDisconnectedAsync(exception);
}
-
- public Task SendProbabilityAsync(Guid deviceId, DateTime date, double probability)
- {
- return Clients.All.NotifyDeviceAsync(deviceId, date, probability);
- }
-
- public Task SendDeviceUpdateAsync(Guid deviceId)
- {
- return Clients.All.NotifyDeviceUpdatedAsync(deviceId);
- }
-
- public Task SendAllUpdatedAsync()
- {
- return Clients.All.NotifyAllUpdatedAsync();
- }
}
}
diff --git a/Birdmap.API/Services/Hubs/IServicesHubClient.cs b/Birdmap.API/Services/Hubs/IServicesHubClient.cs
new file mode 100644
index 0000000..6891865
--- /dev/null
+++ b/Birdmap.API/Services/Hubs/IServicesHubClient.cs
@@ -0,0 +1,9 @@
+using System.Threading.Tasks;
+
+namespace Birdmap.API.Services.Hubs
+{
+ public interface IServicesHubClient
+ {
+ Task NotifyUpdatedAsync();
+ }
+}
diff --git a/Birdmap.API/Services/Hubs/ServicesHub.cs b/Birdmap.API/Services/Hubs/ServicesHub.cs
new file mode 100644
index 0000000..17f3ae2
--- /dev/null
+++ b/Birdmap.API/Services/Hubs/ServicesHub.cs
@@ -0,0 +1,31 @@
+using Microsoft.AspNetCore.SignalR;
+using Microsoft.Extensions.Logging;
+using System;
+using System.Threading.Tasks;
+
+namespace Birdmap.API.Services.Hubs
+{
+ public class ServicesHub : Hub
+ {
+ private readonly ILogger _logger;
+
+ public ServicesHub(ILogger logger)
+ {
+ _logger = logger;
+ }
+
+ public override Task OnConnectedAsync()
+ {
+ _logger.LogInformation("Services Hub Client connected.");
+
+ return base.OnConnectedAsync();
+ }
+
+ public override Task OnDisconnectedAsync(Exception exception)
+ {
+ _logger.LogInformation("Services Hub Client disconnected.");
+
+ return base.OnDisconnectedAsync(exception);
+ }
+ }
+}
diff --git a/Birdmap.API/Services/IMqttClientService.cs b/Birdmap.API/Services/IMqttClientService.cs
index a8e4676..8316d9a 100644
--- a/Birdmap.API/Services/IMqttClientService.cs
+++ b/Birdmap.API/Services/IMqttClientService.cs
@@ -10,6 +10,6 @@ namespace Birdmap.API.Services
IMqttClientDisconnectedHandler,
IMqttApplicationMessageReceivedHandler
{
-
+ public bool IsConnected { get; }
}
}
diff --git a/Birdmap.API/Services/Mqtt/MqttClientService.cs b/Birdmap.API/Services/Mqtt/MqttClientService.cs
index 6ca123e..2ed4abf 100644
--- a/Birdmap.API/Services/Mqtt/MqttClientService.cs
+++ b/Birdmap.API/Services/Mqtt/MqttClientService.cs
@@ -9,7 +9,6 @@ using MQTTnet.Client.Disconnecting;
using MQTTnet.Client.Options;
using Newtonsoft.Json;
using System;
-using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -24,6 +23,8 @@ namespace Birdmap.API.Services.Mqtt
private readonly IInputService _inputService;
private readonly IHubContext _hubContext;
+ public bool IsConnected => _mqttClient.IsConnected;
+
public MqttClientService(IMqttClientOptions options, ILogger logger, IInputService inputService, IHubContext hubContext)
{
_options = options;
diff --git a/Birdmap.API/Startup.cs b/Birdmap.API/Startup.cs
index 16d53c9..fa906fd 100644
--- a/Birdmap.API/Startup.cs
+++ b/Birdmap.API/Startup.cs
@@ -139,6 +139,7 @@ namespace Birdmap.API
endpoints.MapHealthChecks("/health");
endpoints.MapControllers();
endpoints.MapHub("/hubs/devices");
+ endpoints.MapHub("/hubs/services");
});
app.UseSpa(spa =>
diff --git a/Birdmap.API/libman.json b/Birdmap.API/libman.json
new file mode 100644
index 0000000..ceee271
--- /dev/null
+++ b/Birdmap.API/libman.json
@@ -0,0 +1,5 @@
+{
+ "version": "1.0",
+ "defaultProvider": "cdnjs",
+ "libraries": []
+}
\ No newline at end of file
diff --git a/Birdmap.BLL/Interfaces/IServiceService.cs b/Birdmap.BLL/Interfaces/IServiceService.cs
index 7db279b..f281040 100644
--- a/Birdmap.BLL/Interfaces/IServiceService.cs
+++ b/Birdmap.BLL/Interfaces/IServiceService.cs
@@ -6,6 +6,7 @@ namespace Birdmap.BLL.Interfaces
{
public interface IServiceService
{
+ Task GetServiceCountAsync();
Task> GetAllServicesAsync();
Task GetServiceAsync(int id);
Task CreateServiceAsync(Service service);
diff --git a/Birdmap.BLL/Services/DummyDeviceAndInputService.cs b/Birdmap.BLL/Services/DummyDeviceAndInputService.cs
index 00fc2c4..18a86bd 100644
--- a/Birdmap.BLL/Services/DummyDeviceAndInputService.cs
+++ b/Birdmap.BLL/Services/DummyDeviceAndInputService.cs
@@ -16,12 +16,12 @@ namespace Birdmap.BLL.Services
private const double centerLat = 48.275939;
private const double radius = 0.001;
- private static readonly Random Rand = new Random();
+ private static readonly Random Rand = new();
- private static readonly Lazy> Devices = new Lazy>(GenerateDevices);
+ private static readonly Lazy> Devices = new(GenerateDevices);
- private static readonly Dictionary TagToInput = new Dictionary();
- private static readonly object InputLock = new object();
+ private static readonly Dictionary TagToInput = new();
+ private static readonly object InputLock = new();
private static ListOfDevices GenerateDevices()
{
@@ -43,20 +43,20 @@ namespace Birdmap.BLL.Services
var sensors = new ArrayofSensors();
for (int s = 0; s < Rand.Next(1, 6); s++)
{
- sensors.Add(new Sensor
+ sensors.Add(new()
{
Id = Guid.NewGuid(),
Status = GetRandomEnum(),
});
}
- devices.Add(new Device
+ devices.Add(new()
{
Id = Guid.NewGuid(),
Sensors = sensors,
Status = GetRandomEnum(),
Url = "dummyservice.device.url",
- Coordinates = new Coordinates
+ Coordinates = new()
{
Latitude = GetPlusMinus(centerLat, radius),
Longitude = GetPlusMinus(centerLong, radius),
@@ -150,10 +150,10 @@ namespace Birdmap.BLL.Services
{
if (!TagToInput.TryGetValue(tagID, out var value))
{
- value = new InputSingeResponse
+ value = new()
{
Status = "Dummy_OK",
- Message = new InputObject
+ Message = new()
{
Tag = tagID,
Date = DateTime.Now,
diff --git a/Birdmap.BLL/Services/ServiceService.cs b/Birdmap.BLL/Services/ServiceService.cs
index 04c63ea..e2bf0a2 100644
--- a/Birdmap.BLL/Services/ServiceService.cs
+++ b/Birdmap.BLL/Services/ServiceService.cs
@@ -17,6 +17,11 @@ namespace Birdmap.BLL.Services
_context = context;
}
+ public Task GetServiceCountAsync()
+ {
+ return _context.Services.CountAsync();
+ }
+
public async Task CreateServiceAsync(Service service)
{
_context.Services.Add(service);