Merge ArchiveVie branch

This commit is contained in:
Фролов 2021-05-28 12:44:43 +05:00
commit 3757112dc1
22 changed files with 445 additions and 196 deletions

View File

@ -11,7 +11,7 @@
"antd": "^4.15.0", "antd": "^4.15.0",
"chart.js": "^3.0.2", "chart.js": "^3.0.2",
"chartjs-adapter-date-fns": "^1.1.0-beta.1", "chartjs-adapter-date-fns": "^1.1.0-beta.1",
"chartjs-plugin-datalabels": "^2.0.0-beta.1", "chartjs-plugin-datalabels": "^2.0.0-rc.1",
"craco-less": "^1.17.1", "craco-less": "^1.17.1",
"date-fns": "^2.20.0", "date-fns": "^2.20.0",
"moment": "^2.29.1", "moment": "^2.29.1",

View File

@ -32,7 +32,7 @@ export const ChartTimeOnlineFooter = (props) =>{
if(popContent) if(popContent)
spField = <Popover content={popContent}> spField = <Popover content={popContent}>
<div style={{display:"flex"}}> <div className="chart-footer">
<ControlOutlined className='display_label'/> <ControlOutlined className='display_label'/>
{spField} {spField}
</div> </div>

View File

@ -1,11 +1,10 @@
import { useState, useEffect } from 'react'; import { useState, useEffect, useRef } from 'react';
import {CaretUpOutlined, CaretDownOutlined} from '@ant-design/icons' import {CaretUpOutlined, CaretDownOutlined} from '@ant-design/icons'
export const ValueDisplay = ({prefix, value, suffix, isArrowVisible}) =>{ export const ValueDisplay = ({prefix, value, suffix, isArrowVisible}) =>{
const [oldVal, setOldVal] = useState(NaN) const [oldVal, setOldVal] = useState(NaN)
const [val, setVal] = useState('---') const [val, setVal] = useState('---')
const arrowRef = useRef(null);
let arrow = null
useEffect(()=>{ useEffect(()=>{
if(value) if(value)
@ -14,16 +13,16 @@ export const ValueDisplay = ({prefix, value, suffix, isArrowVisible}) =>{
if (isArrowVisible) if (isArrowVisible)
{ {
if (value > oldVal) if (value > oldVal)
arrow = <CaretUpOutlined style={{color:"red"}} /> arrowRef.current = (<CaretUpOutlined style={{color:"red"}} />)
else if (value < oldVal) else if (value < oldVal)
arrow = <CaretDownOutlined style={{color:"red"}} /> arrowRef.current = (<CaretDownOutlined style={{color:"red"}} />)
setOldVal(value) setOldVal(value)
} }
} else } else
setVal(value) setVal(value)
},[value]) },[value])
return(<span className='display_value'>{prefix} {val} {suffix}{arrow}</span>) return(<span className='display_value'>{prefix} {val} {suffix}{arrowRef.current}</span>)
} }
export const Display = (props)=>{ export const Display = (props)=>{

View File

@ -20,8 +20,8 @@ export const ModeDisplay = (props)=>{
value = modeNames[index] ?? index value = modeNames[index] ?? index
} }
return(<> return(<div className="display_header">
<span className="display_label">Режим:</span> <span className="display_label">Режим:</span>
<span className="display_value">{value}</span> <span className="display_value">{value}</span>
</>) </div>)
} }

View File

@ -0,0 +1,24 @@
import { notification } from 'antd';
const typeDictionary = {
'error': 'Ошибка',
'warning': 'Предупрежение',
'info': 'Информация'
}
/**
* Вызов оповещений всплывающим окошком.
* @param body string или ReactNode
* @param type для параметра типа. Допустимые значение 'error', 'warning', 'info'
*/
export default function Notification(body, type='info'){
notification.info({
description: body,
message: typeDictionary[type],
type,
placement: "bottomRight",
duration: 10,
})
}

View File

@ -18,7 +18,7 @@ export default function Well() {
<Menu <Menu
mode="horizontal" mode="horizontal"
selectable={false} selectable={false}
style={{height: '100%', borderRight: 0}} className="well_menu"
> >
<Menu.Item key="1" icon={<FundViewOutlined/>}> <Menu.Item key="1" icon={<FundViewOutlined/>}>
<Link to='telemetry'>Мониторинг</Link> <Link to='telemetry'>Мониторинг</Link>
@ -41,7 +41,7 @@ export default function Well() {
</Menu> </Menu>
<Layout> <Layout>
<Content className="site-layout-background sheet"> <Content className="site-layout-background">
<Switch> <Switch>
<Route path="/well/:id/file"> <Route path="/well/:id/file">
<Files/> <Files/>

View File

@ -46,7 +46,6 @@ export default function WellTreeSelector(props) {
try { try {
let newWells = (await WellService.getWells()).map(w => { return { key: w.id, ...w } }) let newWells = (await WellService.getWells()).map(w => { return { key: w.id, ...w } })
let wellsTree = groupBy(newWells, 'deposit', 'cluster', 'caption') let wellsTree = groupBy(newWells, 'deposit', 'cluster', 'caption')
// setWells( newWells )
setWellsTree(wellsTree) setWellsTree(wellsTree)
} }
catch (e) { catch (e) {

View File

@ -0,0 +1,83 @@
import { useEffect, useState} from 'react';
import {ChartTimeBase, ChartTimeData, ChartTimeDataParams} from './ChartTimeBase'
const GetRandomColor = () => "#" + Math.floor(Math.random()*16777215).toString(16)
function GetOrCreateDatasetByLineConfig(data: ChartTimeData, lineConfig: LineConfig) {
let dataset = data?.datasets.find(d => d.label === lineConfig.label)
if (!dataset) {
let color = lineConfig.borderColor
?? lineConfig.backgroundColor
?? lineConfig.color
?? GetRandomColor()
dataset = {
label: lineConfig.label,
data: [],
backgroundColor: lineConfig.backgroundColor ?? color,
borderColor: lineConfig.borderColor ?? color,
borderWidth: lineConfig.borderWidth ?? 1,
borderDash: lineConfig.dash ?? [],
}
data.datasets.push(dataset);
}
return dataset
}
export type LineConfig = {
type?: string
label: string
units?: string
xAccessorName: string
yAccessorName: string
color?: string
borderColor?: string
backgroundColor?: string
borderWidth?: number
dash?: number[]
labels?: any[]
}
export type ChartTimeProps = {
label?: string,
yDisplay: Boolean,
lines: LineConfig[],
data: any[],
interval: number,
}
export const ChartTimeArchive: React.FC<ChartTimeProps> = (props) => {
const [dataParams, setDataParams] = useState<ChartTimeDataParams>({ data: { datasets: [] }, yStart: new Date(), })
useEffect(() => {
if ((!props?.lines)
|| (!props?.data)
|| (props.lines.length === 0)
|| (props.data.length === 0))
return
setDataParams((preDataParams) => {
props.lines.forEach(lineCfg => {
let dataset = GetOrCreateDatasetByLineConfig(preDataParams.data, lineCfg)
let points = props.data.map(dataItem => {
return {
x: dataItem[lineCfg.xAccessorName],
label:0,
y: new Date(dataItem[lineCfg.yAccessorName])
}
})
dataset.data = points;
});
preDataParams.yStart = new Date()
preDataParams.yStart.setSeconds(preDataParams.yStart.getSeconds() - props.interval)
preDataParams.yInterval = props.interval
preDataParams.displayLabels = props.yDisplay
return preDataParams
})
}, [props.data, props.lines, props.interval, props.yDisplay])
return (<ChartTimeBase dataParams={dataParams} />)
}

View File

@ -11,8 +11,9 @@ import {
ChartTypeRegistry, ChartTypeRegistry,
ChartOptions} from 'chart.js' ChartOptions} from 'chart.js'
import 'chartjs-adapter-date-fns'; import 'chartjs-adapter-date-fns';
import ChartDataLabels from 'chartjs-plugin-datalabels';
Chart.register( TimeScale, LinearScale, LineController, LineElement, PointElement, Legend ); Chart.register( TimeScale, LinearScale, LineController, LineElement, PointElement, Legend, ChartDataLabels );
const defaultOptions = { const defaultOptions = {
//maintainAspectRatio: false, //maintainAspectRatio: false,
@ -63,15 +64,11 @@ const defaultOptions = {
hoverRadius:5, hoverRadius:5,
}, },
}, },
plugins:{
legend:{
display: false,
}
}
} }
export type ChartTimeData = ChartData<keyof ChartTypeRegistry, { export type ChartTimeData = ChartData<keyof ChartTypeRegistry, {
x: number; x: number;
label: number;
y: Date; y: Date;
}[], unknown> }[], unknown>
@ -84,7 +81,8 @@ export type ChartTimeDataParams = {
export type ChartTimeBaseProps = { export type ChartTimeBaseProps = {
dataParams: ChartTimeDataParams, dataParams: ChartTimeDataParams,
options?: ChartOptions<keyof ChartTypeRegistry>, // TODO: Create good type for options
options?: ChartOptions<keyof ChartTypeRegistry> | any,
} }
const timeUnitByInterval = (intervalSec:number):String =>{ const timeUnitByInterval = (intervalSec:number):String =>{
@ -111,6 +109,7 @@ export const ChartTimeBase: React.FC<ChartTimeBaseProps> = ({options, dataParams
let newChart = new Chart(chartRef.current, { let newChart = new Chart(chartRef.current, {
type: 'line', type: 'line',
plugins: [ChartDataLabels],
options: thisOptions, options: thisOptions,
data: dataParams.data data: dataParams.data
}) })

View File

@ -19,6 +19,7 @@ function GetOrCreateDatasetByLineConfig (data: ChartTimeData, lineConfig: LineC
borderColor: lineConfig.borderColor ?? color, borderColor: lineConfig.borderColor ?? color,
borderWidth: lineConfig.borderWidth ?? 1, borderWidth: lineConfig.borderWidth ?? 1,
borderDash: lineConfig.dash ?? [], borderDash: lineConfig.dash ?? [],
showLine: lineConfig.showLine,
} }
data.datasets.push(dataset); data.datasets.push(dataset);
} }
@ -37,6 +38,8 @@ export type LineConfig = {
borderWidth?: number borderWidth?: number
dash?:number[] dash?:number[]
labels?: any[] labels?: any[]
showLine: boolean
xConstValue?: number|null
} }
export type ChartTimeProps = { export type ChartTimeProps = {
@ -64,7 +67,8 @@ export const ChartTimeOnline: React.FC<ChartTimeProps> = (props) => {
props.lines.forEach(lineCfg => { props.lines.forEach(lineCfg => {
let dataset = GetOrCreateDatasetByLineConfig(preDataParams.data, lineCfg) let dataset = GetOrCreateDatasetByLineConfig(preDataParams.data, lineCfg)
let points = props.data.map(dataItem => {return{ let points = props.data.map(dataItem => {return{
x: dataItem[lineCfg.xAccessorName], x: lineCfg.xConstValue ?? dataItem[lineCfg.xAccessorName],
label: dataItem[lineCfg.xAccessorName],
y: new Date(dataItem[lineCfg.yAccessorName])} y: new Date(dataItem[lineCfg.yAccessorName])}
}) })
//dataset.data = [ ...dataset.data, ...points,].slice(-1024) //dataset.data = [ ...dataset.data, ...points,].slice(-1024)
@ -85,6 +89,31 @@ export const ChartTimeOnline: React.FC<ChartTimeProps> = (props) => {
}, [ props.data, props.lines, props.interval, props.yDisplay]) }, [ props.data, props.lines, props.interval, props.yDisplay])
return(<ChartTimeBase dataParams = {dataParams} />) const chartPluginsOptions = {
plugins:{
legend:{
display: false,
},
datalabels: {
backgroundColor: 'transparent',
borderRadius: 4,
color: '#000B',
display: function(context:any) {
return context.dataset.label === 'wellDepth'
? 'auto'
: false
},
formatter: function(value: any, context: any) {
return `${value.y.toLocaleTimeString()} ${value.label.toPrecision(4)}`
},
padding: 6,
align: 'left',
anchor: 'center',
clip: true
}
}
}
return(<ChartTimeBase dataParams = {dataParams} options = { chartPluginsOptions } />)
} }

View File

@ -1,3 +1,11 @@
export default function Analysis(props){ // import {UserOfWells} from "../components/UserOfWells";
return(<h2>Анализ</h2>)
} export default function Analysis(props) {
return (
<div className="menu_title">
<h2>Анализ</h2>
{/*<UserOfWells data={saubData}/>*/}
<hr/>
</div>
)
}

View File

@ -1,3 +1,7 @@
export default function Files(props) { export default function Files(props) {
return (<h2>Файлы</h2>) return (
<div className="menu-title">
<h2>Файлы</h2>
</div>
)
} }

View File

@ -1,6 +1,5 @@
import { useState } from 'react';
import { Layout, Button } from 'antd' import { Layout, Button } from 'antd'
import { UserOutlined, MenuOutlined } from '@ant-design/icons' import { UserOutlined } from '@ant-design/icons'
import logo from '../images/logo_32.png' import logo from '../images/logo_32.png'
import { Link } from "react-router-dom" import { Link } from "react-router-dom"
import WellTreeSelector from '../components/WellTreeSelector' import WellTreeSelector from '../components/WellTreeSelector'
@ -8,7 +7,6 @@ import WellTreeSelector from '../components/WellTreeSelector'
const { Header } = Layout const { Header } = Layout
export default function PageHeader(props){ export default function PageHeader(props){
const [sidebarVisible, setSidebarVisible] = useState(true)
const login = localStorage['login'] const login = localStorage['login']
let handleLogout = () => { let handleLogout = () => {
@ -19,7 +17,6 @@ export default function PageHeader(props){
return( return(
<Layout> <Layout>
<Header className="header"> <Header className="header">
<Button icon={<MenuOutlined />} onClick={()=>setSidebarVisible(!sidebarVisible)}/>
<img src={logo} alt="АСБ" className="logo"/> <img src={logo} alt="АСБ" className="logo"/>
<WellTreeSelector /> <WellTreeSelector />
<h1 className="title">Мониторинг</h1> <h1 className="title">Мониторинг</h1>

View File

@ -1,20 +1,18 @@
import { Layout, } from 'antd' import {Layout} from 'antd'
import Wells from './Wells' import Wells from './Wells'
import PageHeader from './Header' import PageHeader from './Header'
import Well from "../components/Well"; import Well from "../components/Well";
import {Redirect, Route, Switch} from "react-router-dom"; import {Redirect, Route, Switch} from "react-router-dom";
const { Content } = Layout const {Content} = Layout
export default function Main(){ export default function Main() {
return( return (
<Content> <Content>
<PageHeader /> <PageHeader/>
<Layout> <Layout>
<Content className="site-layout-background sheet" <Content className="site-layout-background sheet">
style={{ marginTop: '0',
paddingTop: '0'}}>
<Switch> <Switch>
<Route path="/well/:id/"> <Route path="/well/:id/">
<Well/> <Well/>
@ -26,8 +24,8 @@ export default function Main(){
<Redirect to={{pathname: `/well`}}/> <Redirect to={{pathname: `/well`}}/>
</Route> </Route>
</Switch> </Switch>
</Content> </Content>
</Layout> </Layout>
</Content> </Content>
); )
} }

View File

@ -1,18 +1,22 @@
import { Table } from 'antd'; import {Button, Table, Select} from 'antd';
import { MessageService } from '../services/api' import {MessageService} from '../services/api'
import { useState, useEffect } from 'react' import {useState, useEffect} from 'react'
import { useParams } from 'react-router-dom' import {useParams} from 'react-router-dom'
import { Subscribe } from '../services/signalr' import {Subscribe} from '../services/signalr'
import Loader from '../components/Loader' import Loader from '../components/Loader'
import moment from 'moment'; import moment from 'moment'
import '../styles/message.css' import '../styles/message.css'
import Notification from '../components/Notification'
const {Option} = Select
// Словарь категорий для строк таблицы // Словарь категорий для строк таблицы
const categoryDictionary = { const categoryDictionary = {
1: {title:'Авария'}, 1: {title: 'Авария'},
2: {title:'Предупреждение'}, 2: {title: 'Предупреждение'},
3: {title:'Информация'}, 3: {title: 'Информация'},
} }
// Конфигурация таблицы // Конфигурация таблицы
@ -22,8 +26,6 @@ const columns = [
key: 'date', key: 'date',
dataIndex: 'date', dataIndex: 'date',
render: (item) => moment(item).format('DD MMM YYYY, HH:MM:ss'), render: (item) => moment(item).format('DD MMM YYYY, HH:MM:ss'),
sorter: (a, b) => new Date(b.date) - new Date(a.date),
sortDirections: ['descend', 'ascend'],
}, },
{ {
title: 'Категория', title: 'Категория',
@ -31,14 +33,16 @@ const columns = [
dataIndex: 'categoryId', dataIndex: 'categoryId',
render: (_, item) => categoryDictionary[item.categoryId].title, render: (_, item) => categoryDictionary[item.categoryId].title,
style: (_, item) => categoryDictionary[item.categoryId].style, style: (_, item) => categoryDictionary[item.categoryId].style,
sorter: (a, b) => a.categoryId - b.categoryId, filters: [
sortDirections: ['descend', 'ascend'], {text: 'Авария', value: '1'},
{text: 'Предупреждение', value: '2'},
{text: 'Информация', value: '3'},
]
}, },
{ {
title: 'Сообщение', title: 'Сообщение',
key: 'message', key: 'message',
dataIndex: 'message', dataIndex: 'message',
onFilter: (value, record) => record.name.indexOf(value) === 0,
}, },
{ {
title: 'Пользователь', title: 'Пользователь',
@ -47,12 +51,22 @@ const columns = [
}, },
]; ];
// Опции для фильра
// Данные для таблицы // Данные для таблицы
export default function Messages(props) { export default function Messages() {
let { id } = useParams() let {id} = useParams()
const [messages, setMessages] = useState([]) const [messages, setMessages] = useState([])
const [loader] = useState(false) const [loader] = useState(false)
const filterOptions = [
{label: 'Авария', value: 1},
{label: 'Предупреждение', value: 2},
{label: 'Информация', value: 3},
]
const children = filterOptions.map((line) => (<Option key={line.value}>{line.label}</Option>))
const handleReceiveMessages = (messages) => { const handleReceiveMessages = (messages) => {
if (messages) { if (messages) {
setMessages(messages.items) setMessages(messages.items)
@ -62,28 +76,51 @@ export default function Messages(props) {
useEffect(() => { useEffect(() => {
MessageService.getMessage(id) MessageService.getMessage(id)
.then(handleReceiveMessages) .then(handleReceiveMessages)
.catch(error => console.error(error)) .catch((ex) => {
Notification(`Не удалось загрузить сообщения по скважине "${id}"`, 'error')
console.log(ex)
})
let unSubscribeMessagesHub = Subscribe('ReceiveMessages', `well_${id}`, handleReceiveMessages) let unSubscribeMessagesHub = Subscribe('ReceiveMessages', `well_${id}`, handleReceiveMessages)
return () => { return () => {
unSubscribeMessagesHub() unSubscribeMessagesHub()
} }
}, [id]); }, [id])
return ( return (
<> <>
<h2>Сообщения</h2> <h2>Сообщения</h2>
<hr /> <hr/>
<Table <h3>Фильтр сообщений</h3>
columns={columns} <Select
dataSource={messages} mode="multiple"
rowClassName={(record) => `event_message_${record.categoryId}`} allowClear
size={'small'} placeholder="Фильтр сообщений"
pagination={{ pageSize: 10 }} className="filter-selector"
rowKey={(record) => record.id} // onChange={messagesFilter}
/> >
{loader && <Loader />} {children}
</> </Select>
<Button
type="primary"
className="submit-button"
>
Применть
</Button>
<Button
className="submit-button"
>
Обновить
</Button>
<Table
columns={columns}
dataSource={messages}
rowClassName={(record) => `event_message_${record.categoryId} event_message`}
size={'small'}
pagination={{pageSize: 26}}
rowKey={(record) => record.id}
/>
{loader && <Loader/>}
</>
) )
} }
// TODO Стили для отсортированных страниц

View File

@ -1,4 +1,4 @@
import React from "react"; // , { useState } import React from "react";
import { import {
DatePicker, DatePicker,
Radio, Radio,
@ -37,8 +37,6 @@ const PeriodOfTime = () => {
function disabledDate(current) { function disabledDate(current) {
return current && current < moment().subtract(3, 'days'); // Наверно, не надо) return current && current < moment().subtract(3, 'days'); // Наверно, не надо)
} }
// const [startDate, setStartDate] = useState(new Date());
return ( return (
<ConfigProvider locale={locale} <ConfigProvider locale={locale}
@ -97,12 +95,9 @@ export default function Report(props) {
<p>*Предполагаемое колличество страниц</p> <p>*Предполагаемое колличество страниц</p>
<hr /> <hr />
<div> <div>
<Button type="primary" style={{ <Button type="primary" className="submit_button">
display: 'flex', Получить рапорт
alignItems: 'center', </Button>
padding: '20px',
borderRadius: '5px'
}}>Получить рапорт</Button>
</div> </div>
</> </>

View File

@ -1,89 +1,128 @@
import { useState, useEffect } from 'react'; import {useState, useEffect} from 'react'
import { Row, Col, Select, } from 'antd' import {useParams} from 'react-router-dom'
import { ChartTimeOnline } from '../components/charts/ChartTimeOnline' import {Row, Col, Select, Table} from 'antd'
import { ChartTimeOnlineFooter } from '../components/ChartTimeOnlineFooter' import {ChartTimeOnline} from '../components/charts/ChartTimeOnline'
import { CustomColumn } from '../components/CustomColumn'
import { UserOfWells } from '../components/UserOfWells'
import { ModeDisplay } from '../components/ModeDisplay'
import { Display } from '../components/Display'
import moment from 'moment'
import { Table } from 'antd';
import { MessageService } from '../services/api'
import Loader from '../components/Loader' import Loader from '../components/Loader'
import {ChartTimeOnlineFooter} from '../components/ChartTimeOnlineFooter'
import {CustomColumn} from '../components/CustomColumn'
import {UserOfWells} from '../components/UserOfWells'
import {ModeDisplay} from '../components/ModeDisplay'
import {Display} from '../components/Display'
import moment from 'moment'
import {Subscribe} from '../services/signalr'
import {DataService, MessageService} from '../services/api'
import '../styles/message.css'
import Notification from "../components/Notification";
import { useParams } from 'react-router-dom' const {Option} = Select
import { Subscribe } from '../services/signalr'
import { DataService } from '../services/api'
import '../styles/message_telemetry.css'
const { Option } = Select
const dash = [7, 3] const dash = [7, 3]
const blockHeightGroup = { const blockHeightGroup = {
label: "Высота блока", label: "Высота блока",
yDisplay: false, yDisplay: false,
linePv: { label: "blockHeight", units: 'м', xAccessorName: "blockHeight", yAccessorName: "date", color: '#333' }, linePv: { label: "blockPosition", units: 'м', xAccessorName: "blockPosition", yAccessorName: "date", color: '#333' },
lineOther: { label: "wellDepth", units: 'м', xAccessorName: "wellDepth", yAccessorName: "date", color: '#333', showLine: false, xConstValue:30 },
} }
const blockSpeedGroup = { const blockSpeedGroup = {
label: "Скорость блока", label: "Скорость блока",
yDisplay: false, yDisplay: false,
linePv: { label: "blockSpeed", units: 'м/ч', xAccessorName: "blockSpeed", yAccessorName: "date", color: '#0a0' }, linePv: {label: "blockSpeed", units: 'м/ч', xAccessorName: "blockSpeed", yAccessorName: "date", color: '#0a0'},
lineSp: { label: "blockSpeedSp", units: 'м/ч', xAccessorName: "blockSpeedSp", yAccessorName: "date", color: '#0a0' }, lineSp: {label: "blockSpeedSp", units: 'м/ч', xAccessorName: "blockSpeedSp", yAccessorName: "date", color: '#0a0'},
} }
const pressureGroup = { const pressureGroup = {
label: "Давление", label: "Давление",
yDisplay: false, yDisplay: false,
linePv: { label: "pressure", units: 'атм', xAccessorName: "pressure", yAccessorName: "date", color: '#c00' }, linePv: {label: "pressure", units: 'атм', xAccessorName: "pressure", yAccessorName: "date", color: '#c00'},
lineSp: { label: "pressureSp", units: 'атм', xAccessorName: "pressureSp", yAccessorName: "date", color: '#c00' }, lineSp: {label: "pressureSp", units: 'атм', xAccessorName: "pressureSp", yAccessorName: "date", color: '#c00'},
lineIdle: { label: "pressureIdle", units: 'атм', xAccessorName: "pressureIdle", yAccessorName: "date", color: '#c00' }, lineIdle: {label: "pressureIdle", units: 'атм', xAccessorName: "pressureIdle", yAccessorName: "date", color: '#c00'},
linesOther: [ linesOther: [
{ label: "мекс. перепад", units: 'атм', xAccessorName: "pressureDeltaLimitMax", yAccessorName: "date", color: '#c00' }, {
label: "мекс. перепад",
units: 'атм',
xAccessorName: "pressureDeltaLimitMax",
yAccessorName: "date",
color: '#c00'
},
], ],
} }
const axialLoadGroup = { const axialLoadGroup = {
label: "Осевая нагрузка", label: "Осевая нагрузка",
yDisplay: false, yDisplay: false,
linePv: { label: "axialLoad", units: 'т', xAccessorName: "axialLoad", yAccessorName: "date", color: '#00a' }, linePv: {label: "axialLoad", units: 'т', xAccessorName: "axialLoad", yAccessorName: "date", color: '#00a'},
lineSp: { label: "axialLoadSp", units: 'т', xAccessorName: "axialLoadSp", yAccessorName: "date", color: '#00a', dash }, lineSp: {label: "axialLoadSp", units: 'т', xAccessorName: "axialLoadSp", yAccessorName: "date", color: '#00a', dash},
linesOther: [ linesOther: [
{ label: "axialLoadLimitMax", units: 'т', xAccessorName: "axialLoadLimitMax", yAccessorName: "date", color: '#00a' }, {label: "axialLoadLimitMax", units: 'т', xAccessorName: "axialLoadLimitMax", yAccessorName: "date", color: '#00a'},
], ],
} }
const hookWeightGroup = { const hookWeightGroup = {
label: "Ввес на крюке", label: "Ввес на крюке",
yDisplay: false, yDisplay: false,
linePv: { label: "hookWeight", units: 'т', xAccessorName: "hookWeight", yAccessorName: "date", color: '#0aa' }, linePv: {label: "hookWeight", units: 'т', xAccessorName: "hookWeight", yAccessorName: "date", color: '#0aa'},
lineIdle: { label: "hookWeightIdle", units: 'т', xAccessorName: "hookWeightIdle", yAccessorName: "date", color: '#0aa', dash }, lineIdle: {
label: "hookWeightIdle",
units: 'т',
xAccessorName: "hookWeightIdle",
yAccessorName: "date",
color: '#0aa',
dash
},
linesOther: [ linesOther: [
{ label: "hookWeightLimitMin", units: 'т', xAccessorName: "hookWeightLimitMin", yAccessorName: "date", color: '#0aa' }, {
{ label: "hookWeightLimitMax", units: 'т', xAccessorName: "hookWeightLimitMax", yAccessorName: "date", color: '#0aa' }, label: "hookWeightLimitMin",
units: 'т',
xAccessorName: "hookWeightLimitMin",
yAccessorName: "date",
color: '#0aa'
},
{
label: "hookWeightLimitMax",
units: 'т',
xAccessorName: "hookWeightLimitMax",
yAccessorName: "date",
color: '#0aa'
},
], ],
} }
const rotorTorqueGroup = { const rotorTorqueGroup = {
label: "Момент на роторе", label: "Момент на роторе",
yDisplay: false, yDisplay: false,
linePv: { label: "rotorTorque", units: 'кН·м', xAccessorName: "rotorTorque", yAccessorName: "date", color: '#a0a' }, linePv: {label: "rotorTorque", units: 'кН·м', xAccessorName: "rotorTorque", yAccessorName: "date", color: '#a0a'},
lineSp: { label: "rotorTorqueSp", units: 'кН·м', xAccessorName: "rotorTorqueSp", yAccessorName: "date", color: '#a0a' }, lineSp: {label: "rotorTorqueSp", units: 'кН·м', xAccessorName: "rotorTorqueSp", yAccessorName: "date", color: '#a0a'},
lineIdle: { label: "rotorTorqueIdle", units: 'кН·м', xAccessorName: "rotorTorqueIdle", yAccessorName: "date", color: '#a0a' }, lineIdle: {
label: "rotorTorqueIdle",
units: 'кН·м',
xAccessorName: "rotorTorqueIdle",
yAccessorName: "date",
color: '#a0a'
},
linesOther: [ linesOther: [
{ label: "rotorTorqueLimitMax", units: 'кН·м', xAccessorName: "rotorTorqueLimitMax", yAccessorName: "date", color: '#a0a' }, {
label: "rotorTorqueLimitMax",
units: 'кН·м',
xAccessorName: "rotorTorqueLimitMax",
yAccessorName: "date",
color: '#a0a'
},
], ],
} }
const paramsGroups = [blockHeightGroup, blockSpeedGroup, pressureGroup, axialLoadGroup, hookWeightGroup, rotorTorqueGroup] const paramsGroups = [blockHeightGroup, blockSpeedGroup, pressureGroup, axialLoadGroup, hookWeightGroup, rotorTorqueGroup]
export const Column = ({ lineGroup, data, interval }) => { export const Column = ({lineGroup, data, interval}) => {
let lines = [lineGroup.linePv] let lines = [lineGroup.linePv]
if (lineGroup.lineSp) if (lineGroup.lineSp)
lines.push(lineGroup.lineSp) lines.push(lineGroup.lineSp)
if (lineGroup.lineOther)
lines.push(lineGroup.lineOther)
let dataLast = null let dataLast = null
let pv = null let pv = null
if (data?.length > 0) { if (data?.length > 0) {
@ -102,7 +141,7 @@ export const Column = ({ lineGroup, data, interval }) => {
data={data} data={data}
yDisplay={lineGroup.yDisplay} yDisplay={lineGroup.yDisplay}
lines={lines} lines={lines}
interval={interval} /> interval={interval}/>
<ChartTimeOnlineFooter <ChartTimeOnlineFooter
data={dataLast} data={dataLast}
{...lineGroup} /> {...lineGroup} />
@ -111,9 +150,9 @@ export const Column = ({ lineGroup, data, interval }) => {
// Словарь категорий для строк таблицы // Словарь категорий для строк таблицы
const categoryDictionary = { const categoryDictionary = {
1: { title: 'Авария' }, 1: {title: 'Авария'},
2: { title: 'Предупреждение' }, 2: {title: 'Предупреждение'},
3: { title: 'Информация' }, 3: {title: 'Информация'},
} }
// Конфигурация таблицы // Конфигурация таблицы
@ -145,7 +184,7 @@ const columns = [
]; ];
export default function TelemetryView(props) { export default function TelemetryView(props) {
let { id } = useParams() let {id} = useParams()
const [saubData, setSaubData] = useState([]) const [saubData, setSaubData] = useState([])
const [chartInterval, setChartInterval] = useState(600) const [chartInterval, setChartInterval] = useState(600)
const [messages, setMessages] = useState([]) const [messages, setMessages] = useState([])
@ -167,11 +206,17 @@ export default function TelemetryView(props) {
useEffect(() => { useEffect(() => {
DataService.getData(id) DataService.getData(id)
.then(handleReceiveDataSaub) .then(handleReceiveDataSaub)
.catch(error => console.error(error)) .catch((ex) => {
Notification(`Не удалось загрузить данные по скважине "${id}"`, 'error')
console.log(ex)
})
MessageService.getMessage(id) MessageService.getMessage(id)
.then(handleReceiveMessages) .then(handleReceiveMessages)
.catch(error => console.error(error)) .catch((ex) => {
Notification(`Не удалось загрузить сообщения по скважине "${id}"`, 'error')
console.log(ex)
})
let unSubscribeDataSaubHub = Subscribe('ReceiveDataSaub', `well_${id}`, handleReceiveDataSaub) let unSubscribeDataSaubHub = Subscribe('ReceiveDataSaub', `well_${id}`, handleReceiveDataSaub)
let unSubscribeMessagesHub = Subscribe('ReceiveMessages', `well_${id}`, handleReceiveMessages) let unSubscribeMessagesHub = Subscribe('ReceiveMessages', `well_${id}`, handleReceiveMessages)
@ -181,14 +226,20 @@ export default function TelemetryView(props) {
} }
}, [id]); }, [id]);
useEffect(() => {
DataService.getData(id, null, chartInterval)
.then(handleReceiveDataSaub)
.catch(error => console.error(error))
}, [chartInterval]);
const colSpan = 24 / (paramsGroups.length) const colSpan = 24 / (paramsGroups.length)
return (<div> return (<div>
{/* <div>Well id: {id}; points count: {saubData.length}</div> */} <Row style={{marginBottom: '1rem'}}>
<Row style={{ marginBottom: '1rem' }}>
<Col> <Col>
<ModeDisplay data={saubData} /> <ModeDisplay data={saubData}/>
</Col> </Col>
<span style={{flexGrow: 0.1}}>&nbsp;</span>
<Col> <Col>
Интервал:&nbsp; Интервал:&nbsp;
<Select defaultValue="600" onChange={setChartInterval}> <Select defaultValue="600" onChange={setChartInterval}>
@ -199,33 +250,34 @@ export default function TelemetryView(props) {
<Option value='86400'>1 день</Option> <Option value='86400'>1 день</Option>
</Select> </Select>
</Col> </Col>
<span style={{ flexGrow: 1 }}></span> <span style={{flexGrow: 1}}>&nbsp;</span>
<Col> <Col>
<UserOfWells data={saubData}/> <UserOfWells data={saubData}/>
</Col> </Col>
</Row> </Row>
<Row> <Row>
<Col span={2} > <Col span={2}>
<CustomColumn data={saubData} /> <CustomColumn data={saubData}/>
</Col> </Col>
<Col span={24 - 2}> <Col span={24 - 2}>
<Row> <Row>
{paramsGroups.map(group => {paramsGroups.map(group =>
<Col span={colSpan} className='border_small' key={group.label}> <Col span={colSpan} className='border_small' key={group.label}>
<Column data={saubData} lineGroup={group} interval={chartInterval} /> <Column data={saubData} lineGroup={group} interval={chartInterval}/>
</Col>)} </Col>)}
</Row> </Row>
</Col> </Col>
</Row> </Row>
<Table <Table
showHeader={false} showHeader={false}
columns={columns} columns={columns}
dataSource={messages} dataSource={messages}
rowClassName={(record) => `event_message_${record.categoryId}`} rowClassName={(record) => `event_message_${record.categoryId} event_message`}
size={'small'} className={'message_table'}
pagination={false} size={'small'}
rowKey={(record) => record.id} pagination={false}
/> rowKey={(record) => record.id}
{loader && <Loader />} />
{loader && <Loader/>}
</div>) </div>)
} }

View File

@ -5,7 +5,7 @@ import { HubConnectionBuilder, HubConnectionState } from '@microsoft/signalr';
const ConnectionOptions = { const ConnectionOptions = {
accessTokenFactory: () => localStorage['token'], accessTokenFactory: () => localStorage['token'],
//transport:1, transport:1,
} }
const Connection = new HubConnectionBuilder() const Connection = new HubConnectionBuilder()
@ -15,16 +15,6 @@ const Connection = new HubConnectionBuilder()
let connectionPromise: Promise<void> let connectionPromise: Promise<void>
// const GetConnectionAsync = async () => {
// try {
// await Connection.start();
// console.log(Connection.state);
// } catch (err) {
// console.log(err);
// }
// }
const GetConnectionAsync = async () => { const GetConnectionAsync = async () => {
if (Connection.state === HubConnectionState.Disconnected) if (Connection.state === HubConnectionState.Disconnected)
connectionPromise = Connection.start() connectionPromise = Connection.start()
@ -35,32 +25,6 @@ const GetConnectionAsync = async () => {
return Connection return Connection
} }
// const GetConnectionAsync = async () => {
// if (Connection.state === HubConnectionState.Disconnected)
// connectionPromise = Connection.start()
// if (Connection.state !== HubConnectionState.Connected)
// await connectionPromise
// return Connection
// }
// const GetConnectionAsync = async ()=>{
// if(Connection.state === HubConnectionState.Disconnected)
// await Connection.start()
// if(Connection.state === HubConnectionState.Connecting)
// await Connection.onreconnected
// // if(Connection.state === HubConnectionState.Disconnecting)
// // await Connection.onclose
// // if(Connection.state === HubConnectionState.Connected)
// console.log(Connection.state)
// return Connection
// }
type handlerFunction = (...args: any[]) => void; type handlerFunction = (...args: any[]) => void;
type cleanFunction = (...args: any[]) => void; type cleanFunction = (...args: any[]) => void;

View File

@ -11,6 +11,11 @@
#root, .app{min-height:100%;} #root, .app{min-height:100%;}
html {
display: flex;
height: 100%;
}
.login_page{ .login_page{
position: absolute; position: absolute;
height:100%; height:100%;
@ -28,7 +33,7 @@
.header { .header {
display: flex; display: flex;
align-items: center; align-items: center;
padding: 4px 24px; //padding: 4px 24px;
} }
.header .logo { .header .logo {
@ -36,14 +41,15 @@
border-radius: 32px; border-radius: 32px;
padding: 8px 24px; padding: 8px 24px;
margin: 8px 10px; margin: 8px 10px;
box-shadow: #fff 2px; box-shadow: 0 0 2px #fff;
} }
.header .title{ .header .title{
flex-grow: 1; flex-grow: 1;
color: #fff; color: #fff;
text-align: center; text-align: start;
justify-content: baseline; justify-content: start;
margin-left: 100px;
} }
.header button{ .header button{
@ -53,9 +59,9 @@
} }
.sheet{ .sheet{
padding: 24px; padding: 5px 24px;
margin: 8px; min-height: 280px;
min-height: 280; margin: 0 15px;
} }
.site-layout-background { .site-layout-background {
@ -71,14 +77,26 @@
flex-wrap: wrap; flex-wrap: wrap;
flex: auto; flex: auto;
} }
.display_header {
font-size: 16px;
flex-grow: 1;
display: flex;
}
.display_label{ .display_label{
font-size: 16px; font-size: 16px;
color: rgb(70, 70, 70); color: rgb(70, 70, 70);
text-align: left; text-align: left;
justify-content: baseline; justify-content: center;
margin: 1px 1rem 1px 1rem; margin: 1px 1rem 1px 1rem;
flex: auto; flex: auto;
align-items: baseline;
text-overflow: ellipsis;
overflow-x: hidden;
overflow-y: hidden;
display: -webkit-box;
-webkit-box-orient: vertical;
height: 30px;
} }
.display_value{ .display_value{
@ -95,7 +113,35 @@
.display_small_value{ .display_small_value{
color: rgb(50, 50, 50); color: rgb(50, 50, 50);
text-align: right; text-align: right;
justify-content: baseline; justify-content: center;
margin: 1px 1rem 1px 1rem; margin: 1px 1rem 1px 1rem;
flex: auto; flex: auto;
} }
.menu-title,
.chart-footer {
display: flex;
}
.submit-button {
border-radius: 5px;
font-weight: bold;
text-align: center;
margin: 0 5px 5px 0;
}
.well_menu {
margin-top: 0;
padding-top: 0;
height: 100%;
border-right: 0;
}
tr.table_row_size {
height: 10px;
}
.filter-selector {
width: 50%;
margin-right: 5px;
}

View File

@ -0,0 +1,3 @@
.content-sheet {
display: inline-flex;
}

View File

@ -1,3 +1,7 @@
::-webkit-scrollbar {
width: 0;
}
body { body {
margin: 0; margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',

View File

@ -1,5 +1,15 @@
.event_message { /*.ant-table.ant-table-small .ant-table-tbody > tr > td {*/
/* padding: 0;*/
/*}*/
/*.ant-table-tbody > tr > td {*/
/* border-bottom: 0.5px;*/
/*}*/
.event_message > td {
font-size: 14px; font-size: 14px;
padding: 1px !important;
border-bottom: 1px !important;
} }
.event_message_off { .event_message_off {
@ -22,11 +32,9 @@
background: gold; background: gold;
} }
.event_message_3, .event_message_3 {
.event_message_3:hover {
color: #c0c0c0; color: #c0c0c0;
background: #505060; background: #505060;
height: 1px;
} }
td.ant-table-column-sort { td.ant-table-column-sort {