diff --git a/package-lock.json b/package-lock.json index 667a657..e69632f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4189,6 +4189,16 @@ "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==" }, + "chart.js": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.0.2.tgz", + "integrity": "sha512-DR0GmFSlxcFJp/w//ZmbxSduAkH/AqwxoiZxK97KHnWZf6gvsKWS3160WvNMMHYvzW9OXqGWjPjVh1Qu+xDabg==" + }, + "chartjs-adapter-date-fns": { + "version": "1.1.0-beta.1", + "resolved": "https://registry.npmjs.org/chartjs-adapter-date-fns/-/chartjs-adapter-date-fns-1.1.0-beta.1.tgz", + "integrity": "sha512-VNhuZ86kXKOwh61CyRLP7hoFqAR7+gjnrtf7KYLt/Wfh3jIQs14l1h+nagtQoFaabIYIo6UD5/jJb2/J6zOPcw==" + }, "check-types": { "version": "11.1.2", "resolved": "https://registry.npmjs.org/check-types/-/check-types-11.1.2.tgz", @@ -5060,9 +5070,9 @@ } }, "date-fns": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.19.0.tgz", - "integrity": "sha512-X3bf2iTPgCAQp9wvjOQytnf5vO5rESYRXlPIVcgSbtT5OTScPcsf9eZU+B/YIkKAtYr5WeCii58BgATrNitlWg==" + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.20.0.tgz", + "integrity": "sha512-nmA7y6aDH5+fknfJ0G77HQzUSfTPpq4ifq+c9blP9d+X9zs3kNjxC+t3pcbBMGTp262a6PJB3RVjLlxIgoMI+Q==" }, "debug": { "version": "4.3.1", diff --git a/package.json b/package.json index 59ec63c..ddcfe5d 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,10 @@ "@testing-library/react": "^11.2.6", "@testing-library/user-event": "^12.8.3", "antd": "^4.15.0", + "chart.js": "^3.0.2", + "chartjs-adapter-date-fns": "^1.1.0-beta.1", "craco-less": "^1.17.1", + "date-fns": "^2.20.0", "react": "^17.0.2", "react-dom": "^17.0.2", "react-router-dom": "^5.2.0", diff --git a/src/App.js b/src/App.js index fb162ae..5f1893d 100644 --- a/src/App.js +++ b/src/App.js @@ -3,30 +3,15 @@ import React /*, { useContext, createContext, useState }*/ from "react" import { BrowserRouter as Router, Switch, - Route, - Redirect -} from "react-router-dom" + Route} from "react-router-dom" import Login from './pages/Login' import Main from './pages/Main' -import {OpenAPI} from './services/api' +import { OpenAPI } from './services/api' +import { PrivateRoute } from './components/PrivateRoute' OpenAPI.BASE = 'http://localhost:3000' OpenAPI.TOKEN = localStorage['token'] -function PrivateRoute({ children, ...rest }) { - let token = localStorage['token'] - return ( - - token - ? (children) - : () - } - /> - ); -} - export default function App() { return ( diff --git a/src/components/PrivateRoute.jsx b/src/components/PrivateRoute.jsx new file mode 100644 index 0000000..94a9725 --- /dev/null +++ b/src/components/PrivateRoute.jsx @@ -0,0 +1,16 @@ +import React /*, { useContext, createContext, useState }*/ from "react"; +import { + Route, + Redirect +} from "react-router-dom"; + +export function PrivateRoute({ children, ...rest }) { + let token = localStorage['token']; + return ( + token + ? (children) + : ()} /> + ); +} diff --git a/src/components/charts/ChartSaubDataOnline.jsx b/src/components/charts/ChartSaubDataOnline.jsx new file mode 100644 index 0000000..48c1949 --- /dev/null +++ b/src/components/charts/ChartSaubDataOnline.jsx @@ -0,0 +1,69 @@ +import { Chart, TimeScale, Legend, LineController, LineElement, PointElement } from 'chart.js' +import 'chartjs-adapter-date-fns'; +import React, { useEffect, useRef} from 'react'; + +Chart.register( TimeScale, LineController, LineElement, PointElement, Legend ); + +const options = { + //showLine :true, + indexAxis:'y', + //maintainAspectRatio: false, + //responsive:false, + scales: { + y:{ + type: 'time', + time: { + unit: 'second', + displayFormats: { + millisecond: 'HH:mm:ss.SSS', + second: 'HH:mm:ss', + minute: 'HH:mm:ss', + hour: 'dd HH:mm:ss', + day: 'MM.dd HH:mm', + week: 'yy.MM.dd HH:mm', + month: 'yyyy.MM.dd', + quarter: 'yyyy.MM.dd', + year: 'yyyy.MM', + }, + }, + }, + }, +} + +const data= { + datasets: [{ + label: 'Torque', + data: [ + {x: 10, y: '2021-04-09T17:50:34.021679+05:00'}, + {x: 15, y: '2021-04-09T17:50:33.021679+05:00'}, + {x: 20, y: '2021-04-09T17:50:32.021679+05:00'}], + boderColor: 'rgba(0, 99, 132, 1)', + backgroundColor: 'rgba(0, 99, 132, 1)', + }, + { + label: 'Pressure', + data: [ + {x: 11, y: '2021-04-09T17:50:34.021679+05:00'}, + {x: 14, y: '2021-04-09T17:50:33.021679+05:00'}, + {x: 21, y: '2021-04-09T17:50:32.021679+05:00'}], + boderColor: 'rgba(255, 99, 132, 1)', + backgroundColor: 'rgba(255, 99, 132, 1)', + borderDash: [5, 5], + } + ] +} + +export function ChartSaubDataOnline(props){ + const chartRef = useRef(null) + + useEffect(()=>{ + let chart = new Chart(chartRef.current, { + type: 'line', + data, + options}) + //chart.canvas.parentNode.style.height = '128px'; + return _ => chart.destroy() + },[]) + + return() +} \ No newline at end of file diff --git a/src/pages/Login.jsx b/src/pages/Login.jsx index efdb70b..a604aa6 100644 --- a/src/pages/Login.jsx +++ b/src/pages/Login.jsx @@ -32,13 +32,14 @@ export default function Login() { try{ let user = await login(formData) setUser(user) + setLoader(false) history.push('well') }catch(e){ if(e.status === 400) openNotificationError(e.message) console.error(`Error ${e}`) - } - setLoader(false) + setLoader(false) + } } return ( diff --git a/src/pages/Well.jsx b/src/pages/Well.jsx index 4097e84..2b24161 100644 --- a/src/pages/Well.jsx +++ b/src/pages/Well.jsx @@ -1,6 +1,30 @@ +import React, { useState, useEffect} from 'react'; +import {ChartSaubDataOnline} from '../components/charts/ChartSaubDataOnline' + import {useParams} from 'react-router-dom' +import {Subscribe} from '../services/signalr' +import {DataService} from '../services/api' export default function Well(props){ let { id } = useParams(); - return(
Well id: {id}
) + const [saubData, setSaubData] = useState([]) + + const handleReceiveDataSaub = (data)=>{ + setSaubData(pre => [...pre, ...data].slice(-1024)) + } + + useEffect( ()=> { + DataService.get(id) + .then(handleReceiveDataSaub) + .catch(error=>console.error(error)) + + return Subscribe('ReceiveDataSaub', `well_${id}`, handleReceiveDataSaub) + }, + [id]); + + + return(
+ Well id: {id}; points count: {saubData.length} + +
) } \ No newline at end of file diff --git a/src/pages/Wells.jsx b/src/pages/Wells.jsx index 88bfb4f..2fb1f00 100644 --- a/src/pages/Wells.jsx +++ b/src/pages/Wells.jsx @@ -35,8 +35,8 @@ export default function Wells(props){ let updateWellsList = async () => { setLoader(true) try{ - - setWells( await WellService.get()) + var newWells = (await WellService.get()).map(w =>{return {key:w.id, ...w}}) + setWells( newWells ) } catch(e){ console.error(`${e.message}`); diff --git a/src/services/AutoGenerateApi.md b/src/services/AutoGenerateApi.md index fce21d8..22ff9d5 100644 --- a/src/services/AutoGenerateApi.md +++ b/src/services/AutoGenerateApi.md @@ -1 +1 @@ -npx openapi -i http://localhost:5000/swagger/v1/swagger.json -o src/services/openapi \ No newline at end of file +npx openapi -i http://localhost:5000/swagger/v1/swagger.json -o src/services/api \ No newline at end of file diff --git a/src/services/api/index.ts b/src/services/api/index.ts index a1e2c09..75b7718 100644 --- a/src/services/api/index.ts +++ b/src/services/api/index.ts @@ -5,8 +5,13 @@ export { ApiError } from './core/ApiError'; export { OpenAPI } from './core/OpenAPI'; export type { AuthDto } from './models/AuthDto'; +export type { DataSaubBaseDto } from './models/DataSaubBaseDto'; +export type { TelemetryDataDto } from './models/TelemetryDataDto'; +export type { TelemetryInfoDto } from './models/TelemetryInfoDto'; export type { UserTokenDto } from './models/UserTokenDto'; export type { WellDto } from './models/WellDto'; export { AuthService } from './services/AuthService'; +export { DataService } from './services/DataService'; +export { TelemetryService } from './services/TelemetryService'; export { WellService } from './services/WellService'; diff --git a/src/services/api/models/DataSaubBaseDto.ts b/src/services/api/models/DataSaubBaseDto.ts new file mode 100644 index 0000000..dece8d8 --- /dev/null +++ b/src/services/api/models/DataSaubBaseDto.ts @@ -0,0 +1,32 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +export type DataSaubBaseDto = { + date?: string; + mode?: number | null; + wellDepth?: number | null; + bitDepth?: number | null; + blockHeight?: number | null; + blockSpeed?: number | null; + blockSpeedSp?: number | null; + pressure?: number | null; + pressureIdle?: number | null; + pressureSp?: number | null; + pressureDeltaLimitMax?: number | null; + axialLoad?: number | null; + axialLoadSp?: number | null; + axialLoadLimitMax?: number | null; + hookWeight?: number | null; + hookWeightIdle?: number | null; + hookWeightLimitMin?: number | null; + hookWeightLimitMax?: number | null; + rotorTorque?: number | null; + rotorTorqueIdle?: number | null; + rotorTorqueSp?: number | null; + rotorTorqueLimitMax?: number | null; + rotorSpeed?: number | null; + flow?: number | null; + flowIdle?: number | null; + flowDeltaLimitMax?: number | null; +} \ No newline at end of file diff --git a/src/services/api/models/TelemetryDataDto.ts b/src/services/api/models/TelemetryDataDto.ts new file mode 100644 index 0000000..9ffccea --- /dev/null +++ b/src/services/api/models/TelemetryDataDto.ts @@ -0,0 +1,12 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +import type { DataSaubBaseDto } from './DataSaubBaseDto'; + +export type TelemetryDataDto = { + date?: string; + hmiVersion?: string | null; + userName?: string | null; + dataSaub?: Array | null; +} \ No newline at end of file diff --git a/src/services/api/models/TelemetryInfoDto.ts b/src/services/api/models/TelemetryInfoDto.ts new file mode 100644 index 0000000..9d5b74d --- /dev/null +++ b/src/services/api/models/TelemetryInfoDto.ts @@ -0,0 +1,9 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +export type TelemetryInfoDto = { + caption?: string | null; + cluster?: string | null; + deposit?: string | null; +} \ No newline at end of file diff --git a/src/services/api/models/WellDto.ts b/src/services/api/models/WellDto.ts index 1597fc6..c161219 100644 --- a/src/services/api/models/WellDto.ts +++ b/src/services/api/models/WellDto.ts @@ -3,9 +3,9 @@ /* eslint-disable */ export type WellDto = { - id?: number; caption?: string | null; cluster?: string | null; deposit?: string | null; + id?: number; lastData?: any; } \ No newline at end of file diff --git a/src/services/api/services/DataService.ts b/src/services/api/services/DataService.ts new file mode 100644 index 0000000..d44fdd6 --- /dev/null +++ b/src/services/api/services/DataService.ts @@ -0,0 +1,25 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { DataSaubBaseDto } from '../models/DataSaubBaseDto'; +import { request as __request } from '../core/request'; + +export class DataService { + + /** + * Возвращает данные САУБ по скважине + * @param wellId + * @returns DataSaubBaseDto Success + * @throws ApiError + */ + public static async get( +wellId: number, +): Promise> { + const result = await __request({ + method: 'GET', + path: `/api/well/${wellId}/data`, + }); + return result.body; + } + +} \ No newline at end of file diff --git a/src/services/api/services/TelemetryService.ts b/src/services/api/services/TelemetryService.ts new file mode 100644 index 0000000..160f62e --- /dev/null +++ b/src/services/api/services/TelemetryService.ts @@ -0,0 +1,48 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { TelemetryDataDto } from '../models/TelemetryDataDto'; +import type { TelemetryInfoDto } from '../models/TelemetryInfoDto'; +import { request as __request } from '../core/request'; + +export class TelemetryService { + + /** + * Принимает общую информацию по скважине + * @param uid Уникальный идентификатор отправителя + * @param requestBody нформация об отправителе + * @returns any Success + * @throws ApiError + */ + public static async info( +uid: string, +requestBody?: TelemetryInfoDto, +): Promise { + const result = await __request({ + method: 'POST', + path: `/api/telemetry/${uid}/info`, + body: requestBody, + }); + return result.body; + } + + /** + * Принимает данные от разных систем по скважине + * @param uid Уникальный идентификатор отправителя + * @param requestBody Данные + * @returns any Success + * @throws ApiError + */ + public static async data( +uid: string, +requestBody?: TelemetryDataDto, +): Promise { + const result = await __request({ + method: 'POST', + path: `/api/telemetry/${uid}/data`, + body: requestBody, + }); + return result.body; + } + +} \ No newline at end of file diff --git a/src/services/api/services/WellService.ts b/src/services/api/services/WellService.ts index 5fba265..8fb3f9a 100644 --- a/src/services/api/services/WellService.ts +++ b/src/services/api/services/WellService.ts @@ -13,7 +13,7 @@ export class WellService { public static async get(): Promise> { const result = await __request({ method: 'GET', - path: `/api/Well`, + path: `/api/well`, }); return result.body; } diff --git a/src/services/signalr/index.ts b/src/services/signalr/index.ts new file mode 100644 index 0000000..7b9f55b --- /dev/null +++ b/src/services/signalr/index.ts @@ -0,0 +1,70 @@ +import { HubConnectionBuilder, HubConnectionState } from '@microsoft/signalr'; + +// SignalR js api: +//https://docs.microsoft.com/ru-ru/javascript/api/@aspnet/signalr/?view=signalr-js-latest + +const ConnectionOptions = { + accessTokenFactory: () => localStorage['token'], + //transport:1, +} + +const Connection = new HubConnectionBuilder() + .withUrl(`http://localhost:5000/hubs/telemetry`, ConnectionOptions) + .withAutomaticReconnect() + .build(); + +const GetConnectionAsync = async ()=>{ + if(Connection.state !== HubConnectionState.Connected) + await Connection.start() + + if(Connection.state === HubConnectionState.Connected) + return Connection + else + throw new Error('Can`t connect to websocket') +} + +type handlerFunction = (...args: any[]) => void; + +type cleanFunction = (...args: any[]) => void; + +/** Subscribe on some SignalR method (topic). + * @example useEffect(() => Subscribe('methodName', `${id}`, handleNewData), [id]); + * @param {string} methodName name of the method + * @param {string} groupName name of the group + * @param {handlerFunction} handler fires when got new data by subscription + * @return {cleanFunction} unsubscribe function for useEffect cleanup. + */ +const Subscribe = ( + methodName: string, + groupName: string = '', + handler: handlerFunction ):cleanFunction=>{ + + GetConnectionAsync().then(async connection => { + if(groupName) + await connection.send('AddToGroup', groupName) + connection.on(methodName, handler) + }) + + if(groupName) + return () => { + Connection.send('RemoveFromGroup', groupName) + .finally(()=>Connection.off(methodName)) + } + return () => Connection.off(methodName) +} + +/** Invokes some SignalR method. + * @param {string} methodName name of the method + * @param {any[]} args methods arguments + * @return {Promise} Promise + */ +const InvokeAsync = async (methodName:string, ...args:any[]) => { + await GetConnectionAsync() + await Connection.send(methodName, ...args) +} + +export { + Subscribe, + InvokeAsync, + GetConnectionAsync, +} \ No newline at end of file