Skip to content

Commit

Permalink
Implement sensor plot example with interval
Browse files Browse the repository at this point in the history
Implement graph which enables choose a sensor and the display chart for the given interval

Closes: astarte-platform#101

Signed-off-by: rifa sofic <rifa.sofic@secomind.com>
  • Loading branch information
rifasofic committed Sep 4, 2023
1 parent d7997ac commit 6d472a3
Show file tree
Hide file tree
Showing 10 changed files with 44,383 additions and 0 deletions.
43,474 changes: 43,474 additions & 0 deletions examples/sensor-interval/package-lock.json

Large diffs are not rendered by default.

45 changes: 45 additions & 0 deletions examples/sensor-interval/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
{
"name": "sensor-interval",
"version": "0.1.0",
"private": true,
"dependencies": {
"axios": "^0.21.1",
"bootstrap": "^4.6.0",
"jquery": "^3.5.1",
"moment": "^2.29.1",
"named-urls": "^2.0.0",
"plotly.js": "^1.58.4",
"react": "^17.0.1",
"react-bootstrap": "^1.5.0",
"react-datepicker": "^4.16.0",
"react-dom": "^17.0.1",
"react-intl": "^6.4.4",
"react-plotly.js": "^2.5.1",
"react-scripts": "^4.0.2"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"format": "prettier --write \"src/**/*.{js,jsx,css}\""
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"prettier": "^2.2.1"
}
}
11 changes: 11 additions & 0 deletions examples/sensor-interval/public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Sensor Graph Example</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
167 changes: 167 additions & 0 deletions examples/sensor-interval/src/apiHandler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import { reverse } from "named-urls";
import axios from "axios";

export const constant = {
ID: "id",
ALIAS: "alias",
AVAILABLE_SENSORS: "AvailableSensors",
VALUES: "Values",
SAMPLING_RATE: "SamplingRate",
REALM: "realm",
TOKEN: "token",
ENDPOINT: "endpoint",
};
const Endpoint = {
device_alias: "devices-by-alias/:device_alias/",
device_id: "devices/:id?",
interface_by_alias: "devices/:device_alias/interfaces/:interface/",
interface_by_id: "devices/:device_id/interfaces/:interface/",
interface_id_path: "devices/:device_id/interfaces/:interface/:path/value",
interface_alias_path:
"devices/:device_alias/interfaces/:interface/:path/value",
interface_id_interval_path: "devices/:device_id/interfaces/:interface",
};

function getAPIUrl(endPoint, params = null) {
const path = reverse(Endpoint[endPoint], params);
return getEndPoint() + "appengine/v1/" + getRealmName() + "/" + path;
}

function GET(url, params) {
const headers = {
"Content-Type": "application/json",
Authorization: `Bearer ${getAuthToken()}`,
};
return axios.get(url, { headers: headers, params: params });
}

// API Functions

function getDeviceById(id, params = {}) {
const URL = getAPIUrl("device_id", { id: id });
return GET(URL, params);
}

export function getAvailableSensors(device_id, interface_id) {
// Construct the URL to fetch available sensors
const URL = getAPIUrl("interface_id_interval_path", {
device_id: device_id,
interface: interface_id,
});

// Use the GET function to fetch available sensors
return GET(URL)
.then((response) => {
const availableSensors = response.data.data;
return Promise.resolve(availableSensors);
})
.catch((err) => {
console.error("Error fetching available sensors:", err);
return Promise.reject(err);
});
}

export const getDeviceDataById = (id, params = {}) => {
return getDeviceById(id, params)
.then((response) => {
const data = response.data.data;
const interfaces = Object.keys(data.introspection);
const availableIndex = interfaces.findIndex(
(key) => key.search(constant.AVAILABLE_SENSORS) > -1
);
const valueIndex = interfaces.findIndex(
(key) => key.search(constant.VALUES) > -1
);
return Promise.resolve({ valueIndex, availableIndex, interfaces });
})
.catch((err) => {
return Promise.reject(err);
});
};

export const getDeviceDataByAlias = (alias, params = {}) => {
return getDeviceByAlias(alias, params)
.then((response) => {
const data = response.data.data;
const interfaces = Object.keys(data.introspection);
const availableIndex = interfaces.findIndex(
(key) => key.search(constant.AVAILABLE_SENSORS) > -1
);
const valueIndex = interfaces.findIndex(
(key) => key.search(constant.VALUES) > -1
);
return Promise.resolve({ valueIndex, availableIndex, interfaces });
})
.catch((err) => {
return Promise.reject(err);
});
};

function getDeviceByAlias(alias, params = {}) {
const URL = getAPIUrl("device_alias", { device_alias: alias });
return GET(URL, params);
}

export function getInterfaceById(device_id, interface_id, params = {}) {
const URL = getAPIUrl("interface_by_id", {
device_id: device_id,
interface: interface_id,
});
return GET(URL, params).then((response) => response.data.data);
}

export function getSensorValueById(
device_id,
interface_id,
path,
startDate,
endDate,
params = {}
) {
const URL = getAPIUrl("interface_id_path", {
device_id: device_id,
interface: interface_id,
path: path,
startDate: startDate,
endDate: endDate,
});
return GET(URL, params);
}

export function getInterfaceByAlias(device_alias, interface_id, params = {}) {
const URL = getAPIUrl("interface_by_id", {
device_alias: device_alias,
interface: interface_id,
});
return GET(URL, params).then((response) => response.data.data);
}

// LocalStorage Config

export function setAuthToken(token) {
localStorage.setItem(constant.TOKEN, token);
}

export function getAuthToken() {
return localStorage.getItem(constant.TOKEN) || undefined;
}

export function setRealmName(realm_name) {
localStorage.setItem(constant.REALM, realm_name);
}

export function getRealmName() {
return localStorage.getItem(constant.REALM) || undefined;
}

export function setEndPoint(endpoint) {
localStorage.setItem(constant.ENDPOINT, endpoint);
}

export function getEndPoint() {
return localStorage.getItem(constant.ENDPOINT) || undefined;
}

export function isMissingCredentials() {
return !(getEndPoint() && getAuthToken() && getRealmName());
}
44 changes: 44 additions & 0 deletions examples/sensor-interval/src/assets/css/cast.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
body {
font-family: "Montserrat", sans-serif;
}
.sensor-id-search-div,
.list-group-item {
background: #f3f3f3;
}
.main-row .input-group .form-control {
height: 2.5rem;
}

.main-card.card .btn img {
height: 0.9375rem;
}
.main-row .input-group .form-control,
.font-14 {
font-size: 0.875rem;
}
.main-card {
box-shadow: 0 0.0625rem 0.25rem 0 rgba(0, 0, 0, 0.09);
}
.list-div-main .list-group-item,
.list-div-main .list-group-item:first-child {
border-radius: 0;
}
.bg-sensor-theme {
background-color: #6aa8d8;
border: 1px solid #6aa8d8;
}

.font-14:hover {
background-color: #6aa8d8;
border: 1px solid #6aa8d8;
}

.font-14:focus {
box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
outline: 0;
}

.sensor-values-card{
width: 300px;
}

119 changes: 119 additions & 0 deletions examples/sensor-interval/src/components/CredentialsModal.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import React, { Component } from "react";
import { Col, Form, Modal } from "react-bootstrap";
import {
getAuthToken,
getEndPoint,
getRealmName,
setAuthToken,
setEndPoint,
setRealmName,
} from "../apiHandler";

class CredentialsModal extends Component {
state = {
realm_name: undefined,
token: undefined,
endpoint: undefined,
};

handleValue = (e) => {
e.preventDefault();
this.setState({ [e.target.name]: e.target.value });
};

handleSubmit = (e) => {
const form = e.currentTarget;
e.preventDefault();
if (form.checkValidity() === false) {
e.stopPropagation();
}
const { token, realm_name, endpoint } = this.state;
setAuthToken(token);
setRealmName(realm_name);
setEndPoint(new window.URL(endpoint).href);
this.props.handleCredentialModal();
};

componentDidUpdate(prevProps) {
const { visible } = this.props;
if (visible && prevProps.visible !== visible) {
this.setState({
realm_name: getRealmName(),
token: getAuthToken(),
endpoint: getEndPoint(),
});
}
}

render() {
const { visible } = this.props;
const { realm_name, endpoint, token } = this.state;
return (
<Modal
show={visible}
animation={true}
centered={true}
dialogClassName="main-modal"
backdrop="static"
>
<Modal.Body className="p-5">
<Col xs={12}>
<h6 className="text-center font-weight-bold mb-4">
Enter Your Details
</h6>
</Col>
<Form onSubmit={this.handleSubmit}>
<Form.Group controlId="endPoint">
<Form.Label className="mb-1 font-weight-bold">
Endpoint URL
</Form.Label>
<Form.Control
value={endpoint}
required
name="endpoint"
onChange={this.handleValue}
type="text"
placeholder="Enter EndPoint"
className="font-weight-normal rounded"
/>
</Form.Group>
<Form.Group controlId="realmName">
<Form.Label className="mb-1 font-weight-bold">
Realm Name
</Form.Label>
<Form.Control
value={realm_name}
required
name="realm_name"
onChange={this.handleValue}
type="text"
placeholder="Enter Realm Name"
className="font-weight-normal rounded"
/>
</Form.Group>
<Form.Group controlId="token">
<Form.Label className="mb-1 font-weight-bold">Token</Form.Label>
<Form.Control
value={token}
required
onChange={this.handleValue}
name="token"
type="text"
placeholder="Enter Token Number"
className="font-weight-normal rounded"
/>
</Form.Group>
<button
className="mt-3 bg-sensor-theme font-14 text-white py-2 text-uppercase font-weight-normal px-4 rounded text-decoration-none"
type="submit"
>
Submit
</button>
</Form>
</Modal.Body>
</Modal>
);
}
}

export default CredentialsModal;
Loading

0 comments on commit 6d472a3

Please sign in to comment.