forked from ddrilling/asb_cloud_front
Compare commits
1 Commits
dev
...
feature/do
Author | SHA1 | Date | |
---|---|---|---|
98f66dec1f |
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -12,6 +12,5 @@
|
|||||||
"Setpoints",
|
"Setpoints",
|
||||||
"usehooks"
|
"usehooks"
|
||||||
],
|
],
|
||||||
"liveServer.settings.port": 5501,
|
"liveServer.settings.port": 5501
|
||||||
"cSpell.language": "en,ru"
|
|
||||||
}
|
}
|
@ -63,27 +63,6 @@
|
|||||||
const page: ReactNode = lazy(() => import (...))
|
const page: ReactNode = lazy(() => import (...))
|
||||||
```
|
```
|
||||||
|
|
||||||
### 1.4. Работа с репозиторием
|
|
||||||
|
|
||||||
#### 1.4.1. Подготовка к публикации работы по заданию
|
|
||||||
При получений задания необходимо создать для неё ветку, наследуемую от **dev**.
|
|
||||||
Ветка должна именоваться в **kebab-case** и иметь префикс соответствующий типу задачи:
|
|
||||||
* "**feature/**" - для нового функционала или визуала;
|
|
||||||
* "**fix/**" - для багов и любых исправлений.
|
|
||||||
|
|
||||||
Название ветки должно кратко описывать проблему или новые возможности.
|
|
||||||
|
|
||||||
Далее необходимо создать *pull request* на ветку dev от новосозданной и сразу отметить его как WIP.
|
|
||||||
При завершении задания метку WIP необходимо снять.
|
|
||||||
|
|
||||||
#### 1.4.2 Оформление коммита
|
|
||||||
Изменения файлов необходимо разделять на коммиты по общим изменениям и соответствующе его именовать.
|
|
||||||
Если в коммит попадает более одного логического изменения стоит указывать их в виде маркированного списка, например:
|
|
||||||
```
|
|
||||||
* На странице "Мониторинг" и "Архив" сокращено колличество запросов;
|
|
||||||
* Страница "Сообщения" удалена.
|
|
||||||
```
|
|
||||||
|
|
||||||
## 2. JS
|
## 2. JS
|
||||||
1. Методы, константы и переменные документируются в соответствии с `JSDoc`;
|
1. Методы, константы и переменные документируются в соответствии с `JSDoc`;
|
||||||
2. При документации страниц необходимо указать её название, краткое описание и описание получаемых параметров:
|
2. При документации страниц необходимо указать её название, краткое описание и описание получаемых параметров:
|
||||||
|
68
README.md
68
README.md
@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
Установка выполняется одной командой:
|
Установка выполняется одной командой:
|
||||||
```bash
|
```bash
|
||||||
npm ci
|
npm i
|
||||||
```
|
```
|
||||||
|
|
||||||
## 2. Автогенерация сервисов
|
## 2. Автогенерация сервисов
|
||||||
@ -19,12 +19,12 @@ npm ci
|
|||||||
|
|
||||||
Если сервер запущен на текущей машине достаточно написать:
|
Если сервер запущен на текущей машине достаточно написать:
|
||||||
```bash
|
```bash
|
||||||
npm run oul
|
npm run update_openapi
|
||||||
```
|
```
|
||||||
|
|
||||||
Для получения сервисов с основного сервера:
|
Для получения сервисов с основного сервера:
|
||||||
```bash
|
```bash
|
||||||
npm run oug_dev
|
npm run update_openapi_server
|
||||||
```
|
```
|
||||||
|
|
||||||
или же ручной вариант:
|
или же ручной вариант:
|
||||||
@ -36,12 +36,12 @@ npx openapi -i http://{IP_ADDRESS}:{PORT}/swagger/v1/swagger.json -o src/service
|
|||||||
|
|
||||||
На данный момент имеются следующие IP-адреса:
|
На данный момент имеются следующие IP-адреса:
|
||||||
|
|
||||||
| IP-адрес | Команда | Описание |
|
| IP-адрес | Описание |
|
||||||
|:-------------------------|:--------|:------------------------------------|
|
|:-|:-|
|
||||||
| 127.0.0.1:5000 | oul | Локальный адрес вашей машины |
|
| 127.0.0.1:5000 | Локальный адрес вашей машины (привязан к `update_openapi`) |
|
||||||
| 192.168.1.113:5000 | oud | Локальный адрес development-сервера |
|
| 192.168.1.113:5000 | Локальный адрес development-сервера (привязан к `update_openapi_server`) |
|
||||||
| 46.146.207.184:80 | oug_dev | Внешний адрес development-сервера |
|
| 46.146.209.148:89 | Внешний адрес development-сервера |
|
||||||
| cloud.digitaldrilling.ru | oug | Внешний адрес production-сервера |
|
| cloud.digitaldrilling.ru | Внешний адрес production-сервера |
|
||||||
|
|
||||||
## 3. Компиляция production-версии приложения
|
## 3. Компиляция production-версии приложения
|
||||||
После выполнения вышеописанных пунктов приложение готово к компиляции.
|
После выполнения вышеописанных пунктов приложение готово к компиляции.
|
||||||
@ -60,53 +60,3 @@ npm run build
|
|||||||
```bash
|
```bash
|
||||||
npm start
|
npm start
|
||||||
```
|
```
|
||||||
|
|
||||||
## 5. Подготовка к работе с гит репозиторием
|
|
||||||
|
|
||||||
### 5.1. Генерация SSH-ключей
|
|
||||||
Для генерации ключей, в **Git Bash**, либо в **bash** консоли необходимо ввести команду:
|
|
||||||
```bash
|
|
||||||
ssh-keygen
|
|
||||||
```
|
|
||||||
|
|
||||||
Предложенный путь сохранения ключа оставить без изменений
|
|
||||||
|
|
||||||
Пароль для ускорения работы можно не задавать
|
|
||||||
|
|
||||||
После чего публичный ключ необходимо занести ключ в [Gitea](http://46.146.207.184:8080/), в настройках пользователя.
|
|
||||||
|
|
||||||
Чтобы получить публичный ключ необходимо ввести в консоли команду:
|
|
||||||
```bash
|
|
||||||
cat ~/.ssh/id_rsa.pub
|
|
||||||
```
|
|
||||||
|
|
||||||
Далее ключ небходимо проверить, для этого необходимо нажать соответствующую кнопку в Gitea, скопировать и выполнить предложенную команду в консоли, после чего вывод вставить в поле на странице.
|
|
||||||
|
|
||||||
### 5.2. Генерация GPG-ключей
|
|
||||||
Для генерации ключей, в **Git Bash**, либо в **bash** консоли необходимо ввести команду:
|
|
||||||
```bash
|
|
||||||
gpg --full-generate-key
|
|
||||||
```
|
|
||||||
Тип ключа выбираем *RSA and RSA* (по умолчанию 1). Длину ключа рекомендуется задавать 4096. Далее необходимо заполнить все опрошенные данные, пароль оставить пустым.
|
|
||||||
|
|
||||||
После чего публичный ключ необходимо занести ключ в [Gitea](http://46.146.207.184:8080/), в настройках пользователя.
|
|
||||||
|
|
||||||
Чтобы получить публичный ключ необходимо ввести в консоли команду:
|
|
||||||
```bash
|
|
||||||
gpg --export --armor <email>
|
|
||||||
```
|
|
||||||
|
|
||||||
Где вместо `<email>` необходимо подставить электронную почту, указанную к ключу.
|
|
||||||
|
|
||||||
Далее ключ небходимо проверить, для этого необходимо нажать соответствующую кнопку в Gitea, скопировать и выполнить предложенную команду в консоли, после чего вывод вставить в поле на странице.
|
|
||||||
|
|
||||||
### 5.3. Настройка подписания коммитов (требуется GPG-ключ)
|
|
||||||
Перед началом необходимо получить ID GPG-ключа, для этого выполним команду:
|
|
||||||
```bash
|
|
||||||
gpg --list-keys <email>
|
|
||||||
```
|
|
||||||
|
|
||||||
Где вместо `<email>` необходимо подставить электронную почту, указанную к ключу. Из полученного вывода нам нужна только строка под строкой `rsa4096`. Эту строку мы передадим в следующую команду на место `<key-id>`:
|
|
||||||
```bash
|
|
||||||
git config --user.signingkey <key-id>
|
|
||||||
```
|
|
||||||
|
22
package-lock.json
generated
22
package-lock.json
generated
@ -16,7 +16,8 @@
|
|||||||
"react": "^18.1.0",
|
"react": "^18.1.0",
|
||||||
"react-dom": "^18.1.0",
|
"react-dom": "^18.1.0",
|
||||||
"react-router-dom": "^6.3.0",
|
"react-router-dom": "^6.3.0",
|
||||||
"rxjs": "^7.5.5"
|
"rxjs": "^7.5.5",
|
||||||
|
"usehooks-ts": "^2.6.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.18.2",
|
"@babel/core": "^7.18.2",
|
||||||
@ -14687,6 +14688,19 @@
|
|||||||
"requires-port": "^1.0.0"
|
"requires-port": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/usehooks-ts": {
|
||||||
|
"version": "2.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/usehooks-ts/-/usehooks-ts-2.7.2.tgz",
|
||||||
|
"integrity": "sha512-DeLqSnGg9VvpwPZA+6lKVURJKM9EBu7bbIXuYclQ9COO3w4lacnJa0uP0iJbC/lAmY7GlmPinjZfGNNmDTlUpg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.15.0",
|
||||||
|
"npm": ">=8"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
|
||||||
|
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/util-deprecate": {
|
"node_modules/util-deprecate": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||||
@ -26397,6 +26411,12 @@
|
|||||||
"requires-port": "^1.0.0"
|
"requires-port": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"usehooks-ts": {
|
||||||
|
"version": "2.7.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/usehooks-ts/-/usehooks-ts-2.7.2.tgz",
|
||||||
|
"integrity": "sha512-DeLqSnGg9VvpwPZA+6lKVURJKM9EBu7bbIXuYclQ9COO3w4lacnJa0uP0iJbC/lAmY7GlmPinjZfGNNmDTlUpg==",
|
||||||
|
"requires": {}
|
||||||
|
},
|
||||||
"util-deprecate": {
|
"util-deprecate": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||||
|
@ -11,7 +11,8 @@
|
|||||||
"react": "^18.1.0",
|
"react": "^18.1.0",
|
||||||
"react-dom": "^18.1.0",
|
"react-dom": "^18.1.0",
|
||||||
"react-router-dom": "^6.3.0",
|
"react-router-dom": "^6.3.0",
|
||||||
"rxjs": "^7.5.5"
|
"rxjs": "^7.5.5",
|
||||||
|
"usehooks-ts": "^2.6.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
@ -24,11 +25,11 @@
|
|||||||
"dev": "webpack-dev-server --env=\"ENV=dev\" --open --hot",
|
"dev": "webpack-dev-server --env=\"ENV=dev\" --open --hot",
|
||||||
|
|
||||||
"oul": "npx openapi -i http://127.0.0.1:5000/swagger/v1/swagger.json -o src/services/api",
|
"oul": "npx openapi -i http://127.0.0.1:5000/swagger/v1/swagger.json -o src/services/api",
|
||||||
"oud": "npx openapi -i http://192.168.1.10:5000/swagger/v1/swagger.json -o src/services/api",
|
"oud": "npx openapi -i http://192.168.1.113:5000/swagger/v1/swagger.json -o src/services/api",
|
||||||
"oug": "npx openapi -i https://cloud.digitaldrilling.ru/swagger/v1/swagger.json -o src/services/api",
|
"oug": "npx openapi -i https://cloud.digitaldrilling.ru/swagger/v1/swagger.json -o src/services/api",
|
||||||
"oug_dev": "npx openapi -i http://46.146.207.184/swagger/v1/swagger.json -o src/services/api"
|
"oug_dev": "npx openapi -i http://46.146.209.148:89/swagger/v1/swagger.json -o src/services/api"
|
||||||
},
|
},
|
||||||
"proxy": "http://46.146.207.184",
|
"proxy": "http://46.146.209.148:89",
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"extends": [
|
"extends": [
|
||||||
"react-app",
|
"react-app",
|
||||||
|
86
src/components/LiveLog.tsx
Normal file
86
src/components/LiveLog.tsx
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
import { createContext, ReactNode, useCallback, useContext, useMemo, useState } from 'react'
|
||||||
|
import { CloseOutlined } from '@ant-design/icons'
|
||||||
|
import { Button } from 'antd'
|
||||||
|
|
||||||
|
import { FunctionalValue, getFunctionalValue, isDev } from '@utils'
|
||||||
|
|
||||||
|
import '@styles/components/live_log.less'
|
||||||
|
|
||||||
|
export type LogType = 'info' | 'warn' | 'error' | 'critical'
|
||||||
|
export type VarType = { value: any, type: LogType }
|
||||||
|
|
||||||
|
export type LogContext = (varName: string, value: FunctionalValue<(prev?: any) => any>, type?: FunctionalValue<(prev: LogType) => LogType>) => void
|
||||||
|
|
||||||
|
export const LiveLogContext = createContext<LogContext>(() => {})
|
||||||
|
|
||||||
|
const renderVar = (name: string, value: any, type: LogType, remove: (name: string) => void) => {
|
||||||
|
if (typeof value === 'object')
|
||||||
|
value = JSON.stringify(value)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={`log-row log-${type}`}>
|
||||||
|
<span className={'log-close'} title={'Удалить значение'} onClick={() => remove(name)}><CloseOutlined /></span>
|
||||||
|
<span className={'log-name'}>{name}:</span>
|
||||||
|
<span className={'log-value'}>{String(value)}</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LiveLogProps = {
|
||||||
|
placement?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right'
|
||||||
|
children: JSX.Element
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LiveLog = ({ placement = 'top-left', children }: LiveLogProps) => {
|
||||||
|
if (!isDev()) return children
|
||||||
|
|
||||||
|
const [logMsgs, setLogMsgs] = useState<Record<string, VarType | undefined>>({})
|
||||||
|
|
||||||
|
const clear = useCallback(() => setLogMsgs({}), [])
|
||||||
|
|
||||||
|
const removeVar = useCallback((name: string) => {
|
||||||
|
setLogMsgs((prev) => {
|
||||||
|
if (!(name in prev)) return prev
|
||||||
|
const newMsgs = { ...prev }
|
||||||
|
delete newMsgs[name]
|
||||||
|
return newMsgs
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const log: LogContext = useCallback((varName, value, type) => {
|
||||||
|
setLogMsgs((prev) => {
|
||||||
|
const newValue = getFunctionalValue(value)(prev[varName]?.value)
|
||||||
|
const newType = getFunctionalValue(type)(prev[varName]?.type)
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
[varName]: {
|
||||||
|
type: newType || 'info',
|
||||||
|
value: newValue,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const logInner = useMemo(() => Object.entries(logMsgs).map(([name, value]) => value && renderVar(name, value.value, value.type, removeVar)), [logMsgs, removeVar])
|
||||||
|
const isEmpty = useMemo(() => Object.keys(logMsgs).length <= 0, [logMsgs])
|
||||||
|
return (
|
||||||
|
<LiveLogContext.Provider value={log}>
|
||||||
|
{isEmpty || (
|
||||||
|
<div className={`log-box log-box-${placement}`}>
|
||||||
|
<div className={'log-box-label'}>
|
||||||
|
<span className={'log-box-label'}>Непрерывные значения</span>
|
||||||
|
<Button type={'link'} title={'Очистить'} onClick={clear}><CloseOutlined /></Button>
|
||||||
|
</div>
|
||||||
|
<div className={'log-box-content'}>
|
||||||
|
{logInner}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{children}
|
||||||
|
</LiveLogContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useLiveLog = () => useContext(LiveLogContext)
|
||||||
|
|
||||||
|
export default LiveLog
|
@ -1,10 +1,11 @@
|
|||||||
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
|
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
|
import { useElementSize } from 'usehooks-ts'
|
||||||
import { Property } from 'csstype'
|
import { Property } from 'csstype'
|
||||||
import { Empty } from 'antd'
|
import { Empty } from 'antd'
|
||||||
import * as d3 from 'd3'
|
import * as d3 from 'd3'
|
||||||
|
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
import LoaderPortal from '@components/LoaderPortal'
|
||||||
import { isDev, useElementSize, usePartialProps } from '@utils'
|
import { isDev, usePartialProps } from '@utils'
|
||||||
|
|
||||||
import D3MouseZone from './D3MouseZone'
|
import D3MouseZone from './D3MouseZone'
|
||||||
import { getChartClass } from './functions'
|
import { getChartClass } from './functions'
|
||||||
@ -130,7 +131,7 @@ const _D3Chart = <DataType extends Record<string, unknown>>({
|
|||||||
|
|
||||||
const [charts, setCharts] = useState<ChartRegistry<DataType>[]>([])
|
const [charts, setCharts] = useState<ChartRegistry<DataType>[]>([])
|
||||||
|
|
||||||
const [rootRef, { width, height }] = useElementSize<HTMLDivElement>()
|
const [rootRef, { width, height }] = useElementSize()
|
||||||
|
|
||||||
const xAxis = useMemo(() => {
|
const xAxis = useMemo(() => {
|
||||||
if (!data) return
|
if (!data) return
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import { memo, useCallback, useEffect, useMemo, useRef } from 'react'
|
import { memo, useCallback, useEffect, useMemo, useRef } from 'react'
|
||||||
|
import { useElementSize } from 'usehooks-ts'
|
||||||
import { Property } from 'csstype'
|
import { Property } from 'csstype'
|
||||||
import * as d3 from 'd3'
|
import * as d3 from 'd3'
|
||||||
|
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
import LoaderPortal from '@components/LoaderPortal'
|
||||||
import { useElementSize, usePartialProps } from '@utils'
|
import { usePartialProps } from '@utils'
|
||||||
import { ChartOffset } from './types'
|
import { ChartOffset } from './types'
|
||||||
|
|
||||||
import '@styles/components/d3.less'
|
import '@styles/components/d3.less'
|
||||||
@ -33,7 +34,7 @@ export const D3HorizontalPercentChart = memo<D3HorizontalChartProps>(({
|
|||||||
}) => {
|
}) => {
|
||||||
const offset = usePartialProps<ChartOffset>(givenOffset, defaultOffset)
|
const offset = usePartialProps<ChartOffset>(givenOffset, defaultOffset)
|
||||||
|
|
||||||
const [divRef, { width, height }] = useElementSize<HTMLDivElement>()
|
const [divRef, { width, height }] = useElementSize()
|
||||||
const rootRef = useRef<SVGGElement | null>(null)
|
const rootRef = useRef<SVGGElement | null>(null)
|
||||||
|
|
||||||
const root = useCallback(() => rootRef.current ? d3.select(rootRef.current) : null, [rootRef.current])
|
const root = useCallback(() => rootRef.current ? d3.select(rootRef.current) : null, [rootRef.current])
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
|
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
|
import { useElementSize } from 'usehooks-ts'
|
||||||
import { Property } from 'csstype'
|
import { Property } from 'csstype'
|
||||||
import { Empty } from 'antd'
|
import { Empty } from 'antd'
|
||||||
import * as d3 from 'd3'
|
import * as d3 from 'd3'
|
||||||
|
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
import LoaderPortal from '@components/LoaderPortal'
|
||||||
import { isDev, useElementSize, usePartialProps, useUserSettings } from '@utils'
|
import { isDev, usePartialProps, useUserSettings } from '@utils'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
BaseDataType,
|
BaseDataType,
|
||||||
@ -212,7 +213,7 @@ const _D3MonitoringCharts = <DataType extends Record<string, unknown>>({
|
|||||||
const yTicks = usePartialProps<Required<ChartTick<DataType>>>(_yTicks, getDefaultYTicks)
|
const yTicks = usePartialProps<Required<ChartTick<DataType>>>(_yTicks, getDefaultYTicks)
|
||||||
const yAxisConfig = usePartialProps<ChartAxis<DataType>>(_yAxisConfig, getDefaultYAxisConfig)
|
const yAxisConfig = usePartialProps<ChartAxis<DataType>>(_yAxisConfig, getDefaultYAxisConfig)
|
||||||
|
|
||||||
const [rootRef, { width, height }] = useElementSize<HTMLDivElement>()
|
const [rootRef, { width, height }] = useElementSize()
|
||||||
|
|
||||||
const yAxisArea = useCallback(() => d3.select(yAxisRef), [yAxisRef])
|
const yAxisArea = useCallback(() => d3.select(yAxisRef), [yAxisRef])
|
||||||
const axesArea = useCallback(() => d3.select(axesAreaRef), [axesAreaRef])
|
const axesArea = useCallback(() => d3.select(axesAreaRef), [axesAreaRef])
|
||||||
|
@ -3,6 +3,7 @@ import { ConfigProvider } from 'antd'
|
|||||||
import { createRoot } from 'react-dom/client'
|
import { createRoot } from 'react-dom/client'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
|
import LiveLog from '@components/LiveLog'
|
||||||
import { getUser } from '@utils'
|
import { getUser } from '@utils'
|
||||||
import { OpenAPI } from '@api'
|
import { OpenAPI } from '@api'
|
||||||
|
|
||||||
@ -22,7 +23,9 @@ const root = createRoot(container)
|
|||||||
root.render(
|
root.render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<ConfigProvider locale={locale}>
|
<ConfigProvider locale={locale}>
|
||||||
|
<LiveLog>
|
||||||
<App />
|
<App />
|
||||||
|
</LiveLog>
|
||||||
</ConfigProvider>
|
</ConfigProvider>
|
||||||
</React.StrictMode>
|
</React.StrictMode>
|
||||||
)
|
)
|
||||||
|
@ -33,7 +33,7 @@ export const makeMessageColumns = (idWell) => [
|
|||||||
<Tooltip title={'Нажмите для перехода в архив'}>
|
<Tooltip title={'Нажмите для перехода в архив'}>
|
||||||
<Link
|
<Link
|
||||||
style={{ color: 'inherit'}}
|
style={{ color: 'inherit'}}
|
||||||
to={`/well/${idWell}/telemetry/monitoring?range=1800&end=${moment(item?.date).add(27, 'minute').local().toISOString()}`}
|
to={`/well/${idWell}/telemetry/monitoring?range=1800&start=${moment(item?.date).subtract(3, 'minute').local().toISOString()}`}
|
||||||
>
|
>
|
||||||
<LinkOutlined />
|
<LinkOutlined />
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ import { Button, Modal } from 'antd'
|
|||||||
import { useWell } from '@asb/context'
|
import { useWell } from '@asb/context'
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { EditableTable, makeGroupColumn, makeNumericColumn, makeNumericRender, makeSelectColumn } from '@components/Table'
|
import { EditableTable, makeGroupColumn, makeNumericColumn, makeNumericRender, makeSelectColumn } from '@components/Table'
|
||||||
import { OperationValueService } from '@api'
|
import { DetectedOperationService, OperationValueService } from '@api'
|
||||||
import { arrayOrDefault } from '@utils'
|
import { arrayOrDefault } from '@utils'
|
||||||
|
|
||||||
const columnOptions = {
|
const columnOptions = {
|
||||||
@ -14,7 +14,7 @@ const columnOptions = {
|
|||||||
const scroll = { y: '75vh', scrollToFirstRowOnChange: true }
|
const scroll = { y: '75vh', scrollToFirstRowOnChange: true }
|
||||||
const numericRender = makeNumericRender(2)
|
const numericRender = makeNumericRender(2)
|
||||||
|
|
||||||
export const TargetEditor = memo(({ loading, onChange, options }) => {
|
export const TargetEditor = memo(({ loading, onChange }) => {
|
||||||
const [targets, setTargets] = useState([])
|
const [targets, setTargets] = useState([])
|
||||||
const [showModal, setShowModal] = useState(false)
|
const [showModal, setShowModal] = useState(false)
|
||||||
const [showLoader, setShowLoader] = useState(false)
|
const [showLoader, setShowLoader] = useState(false)
|
||||||
@ -62,6 +62,9 @@ export const TargetEditor = memo(({ loading, onChange, options }) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
invokeWebApiWrapperAsync(
|
invokeWebApiWrapperAsync(
|
||||||
async () => {
|
async () => {
|
||||||
|
const categories = arrayOrDefault(await DetectedOperationService.getCategories())
|
||||||
|
const options = categories.map(({ id, name }) => ({ value: id, label: name }))
|
||||||
|
|
||||||
setTargetColumns([
|
setTargetColumns([
|
||||||
makeSelectColumn('Название', 'idOperationCategory', options, undefined, { ...columnOptions, width: 200 }, {
|
makeSelectColumn('Название', 'idOperationCategory', options, undefined, { ...columnOptions, width: 200 }, {
|
||||||
showSearch: true,
|
showSearch: true,
|
||||||
@ -80,7 +83,7 @@ export const TargetEditor = memo(({ loading, onChange, options }) => {
|
|||||||
`Не удалось получить список категорий целей`,
|
`Не удалось получить список категорий целей`,
|
||||||
{ actionName: 'Получение списка категорий целей', well }
|
{ actionName: 'Получение списка категорий целей', well }
|
||||||
)
|
)
|
||||||
}, [options])
|
}, [well])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
updateTable()
|
updateTable()
|
||||||
|
@ -8,7 +8,7 @@ import { DateRangeWrapper } from '@components/Table'
|
|||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { unique } from '@utils/filters'
|
import { unique } from '@utils/filters'
|
||||||
import { getPermissions, arrayOrDefault, range, withPermissions, prettify } from '@utils'
|
import { getPermissions, arrayOrDefault, range, withPermissions, prettify } from '@utils'
|
||||||
import { DetectedOperationService, DrillerService, TelemetryDataSaubService, WellOperationService } from '@api'
|
import { DetectedOperationService, DrillerService, TelemetryDataSaubService } from '@api'
|
||||||
|
|
||||||
import DrillerList from './DrillerList'
|
import DrillerList from './DrillerList'
|
||||||
import TargetEditor from './TargetEditor'
|
import TargetEditor from './TargetEditor'
|
||||||
@ -26,7 +26,7 @@ const Operations = memo(() => {
|
|||||||
const [data, setData] = useState({})
|
const [data, setData] = useState({})
|
||||||
const [drillers, setDrillers] = useState([])
|
const [drillers, setDrillers] = useState([])
|
||||||
const [drillersLoader, setDrillersLoader] = useState(false)
|
const [drillersLoader, setDrillersLoader] = useState(false)
|
||||||
const [selectedCategory, setSelectedCategory] = useState(5011)
|
const [selectedCategory, setSelectedCategory] = useState(14)
|
||||||
const [categories, setCategories] = useState()
|
const [categories, setCategories] = useState()
|
||||||
|
|
||||||
const [well] = useWell()
|
const [well] = useWell()
|
||||||
@ -78,7 +78,7 @@ const Operations = memo(() => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
invokeWebApiWrapperAsync(
|
invokeWebApiWrapperAsync(
|
||||||
async () => {
|
async () => {
|
||||||
const categories = arrayOrDefault(await WellOperationService.getCategories(well.id))
|
const categories = arrayOrDefault(await DetectedOperationService.getCategories())
|
||||||
setCategories(categories.map((category) => ({
|
setCategories(categories.map((category) => ({
|
||||||
...category,
|
...category,
|
||||||
value: category.id,
|
value: category.id,
|
||||||
@ -86,10 +86,10 @@ const Operations = memo(() => {
|
|||||||
})))
|
})))
|
||||||
},
|
},
|
||||||
setIsLoading,
|
setIsLoading,
|
||||||
'Не удалось загрузить категории операций',
|
'Не удалось загрзуить категории операций',
|
||||||
{ actionName: 'Получение категорий операций' }
|
{ actionName: 'Получение категорий операций' }
|
||||||
)
|
)
|
||||||
}, [well])
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
invokeWebApiWrapperAsync(
|
invokeWebApiWrapperAsync(
|
||||||
@ -135,7 +135,6 @@ const Operations = memo(() => {
|
|||||||
onChange={setYDomain}
|
onChange={setYDomain}
|
||||||
addonAfter={'мин'}
|
addonAfter={'мин'}
|
||||||
addonBefore={'Верхняя граница'}
|
addonBefore={'Верхняя граница'}
|
||||||
style={{width: '20em'}}
|
|
||||||
/>
|
/>
|
||||||
{permissions.driller.get && (
|
{permissions.driller.get && (
|
||||||
<>
|
<>
|
||||||
@ -143,8 +142,8 @@ const Operations = memo(() => {
|
|||||||
<DrillerList drillers={drillers} loading={drillersLoader} onChange={updateDrillers} />
|
<DrillerList drillers={drillers} loading={drillersLoader} onChange={updateDrillers} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{permissions.detectedOperation.get && permissions.operationValue.get && categories && (
|
{permissions.detectedOperation.get && permissions.operationValue.get && (
|
||||||
<TargetEditor onChange={updateData} options={categories} />
|
<TargetEditor onChange={updateData} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<LoaderPortal show={isLoading}>
|
<LoaderPortal show={isLoading}>
|
||||||
|
@ -1,269 +0,0 @@
|
|||||||
import { Button, Input, Modal, Radio, Table } from 'antd'
|
|
||||||
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
|
|
||||||
import moment from 'moment'
|
|
||||||
import * as d3 from 'd3'
|
|
||||||
|
|
||||||
import { useWell } from '@asb/context'
|
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
|
||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
|
||||||
import { DateRangeWrapper, makeColumn, makeNumericColumn, makeNumericRender, makeTextColumn } from '@components/Table'
|
|
||||||
import { LimitingParameterService } from '@api'
|
|
||||||
import { unique } from '@utils/filters'
|
|
||||||
import { useElementSize } from '@utils'
|
|
||||||
|
|
||||||
import { makeGetColor } from '@pages/Well/WellOperations/Tvd'
|
|
||||||
|
|
||||||
import '@styles/limiting_parameter_statistics.less'
|
|
||||||
|
|
||||||
const columns = [
|
|
||||||
makeColumn('Цвет', 'color', { width: 50, render: (d) => (
|
|
||||||
<div style={{ backgroundColor: d, padding: '5px 0' }} />
|
|
||||||
) }),
|
|
||||||
makeTextColumn('Уставка', 'nameFeedRegulator'),
|
|
||||||
makeNumericColumn('Проходка, м', 'depth'),
|
|
||||||
makeNumericColumn('Кол-во включений', 'numberInclusions', undefined, undefined, makeNumericRender(0)),
|
|
||||||
]
|
|
||||||
|
|
||||||
export const LimitingParameterStatistics = memo(() => {
|
|
||||||
const [isOpen, setIsOpen] = useState(false)
|
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
|
||||||
const [data, setData] = useState([])
|
|
||||||
const [mode, setMode] = useState('depth')
|
|
||||||
const [depthFilter, setDepthFilter] = useState({ from: null, to: null })
|
|
||||||
const [dateFilter, setDateFilter] = useState([moment().subtract(1, 'day'), moment()])
|
|
||||||
|
|
||||||
const [svgRef, setSvgRef] = useState()
|
|
||||||
const [selectedRegulator, setSelectedRegulator] = useState(null)
|
|
||||||
|
|
||||||
const [ref, { width, height }] = useElementSize()
|
|
||||||
|
|
||||||
const [well] = useWell()
|
|
||||||
|
|
||||||
const onDepthChanged = useCallback((e, type) => {
|
|
||||||
setDepthFilter((prev) => ({ ...prev, [type]: e?.target?.value }))
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const onRow = useCallback((record) => {
|
|
||||||
const out = {
|
|
||||||
onMouseEnter: () => {
|
|
||||||
setSelectedRegulator(record.idFeedRegulator)
|
|
||||||
d3.selectAll('.tl-pie-part')
|
|
||||||
.filter((d) => d.data.idFeedRegulator === record.idFeedRegulator)
|
|
||||||
.attr('transform', 'scale(1.05)')
|
|
||||||
},
|
|
||||||
onMouseLeave: () => {
|
|
||||||
setSelectedRegulator(null)
|
|
||||||
d3.selectAll('.tl-pie-part')
|
|
||||||
.filter((d) => d.data.idFeedRegulator === record.idFeedRegulator)
|
|
||||||
.attr('transform', 'scale(1)')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (record.idFeedRegulator === selectedRegulator)
|
|
||||||
out.style = { background: '#FAFAFA', fontSize: '16px', fontWeight: '600' }
|
|
||||||
return out
|
|
||||||
}, [selectedRegulator])
|
|
||||||
|
|
||||||
const onPieOver = useCallback(function (e, d) {
|
|
||||||
setSelectedRegulator(d.data.idFeedRegulator)
|
|
||||||
d3.select(this).attr('transform', 'scale(1.05)')
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const onPieOut = useCallback(function (e, d) {
|
|
||||||
setSelectedRegulator(null)
|
|
||||||
d3.select(this).attr('transform', 'scale(1)')
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const update = useCallback(() => {
|
|
||||||
invokeWebApiWrapperAsync(
|
|
||||||
async () => {
|
|
||||||
const data = await LimitingParameterService.getStat(well.id,
|
|
||||||
mode === 'time' ? dateFilter[0] : undefined,
|
|
||||||
mode === 'time' ? dateFilter[1] : undefined,
|
|
||||||
mode === 'depth' ? depthFilter.from : undefined,
|
|
||||||
mode === 'depth' ? depthFilter.to : undefined,
|
|
||||||
)
|
|
||||||
setData(data)
|
|
||||||
},
|
|
||||||
setIsLoading,
|
|
||||||
`Не удалось загрузить статистику использования уставок`,
|
|
||||||
{ actionName: `Загрузка статистики использования уставок`, well }
|
|
||||||
)
|
|
||||||
}, [well, mode, dateFilter, depthFilter])
|
|
||||||
|
|
||||||
const pie = useMemo(() => d3.pie().value((d) => d.totalMinutes), [])
|
|
||||||
|
|
||||||
const getColor = useMemo(() => makeGetColor(data?.map((row) => row.idFeedRegulator).filter(unique)), [data])
|
|
||||||
|
|
||||||
const tableData = useMemo(() => {
|
|
||||||
if (!data) return null
|
|
||||||
const totalTime = data.reduce((out, stat) => out + stat.totalMinutes, 0)
|
|
||||||
return data.map((stat) => ({
|
|
||||||
...stat,
|
|
||||||
color: getColor(stat.idFeedRegulator),
|
|
||||||
percent: stat.totalMinutes / totalTime * 100,
|
|
||||||
}))
|
|
||||||
}, [data, getColor])
|
|
||||||
|
|
||||||
const pieData = useMemo(() => tableData ? pie(tableData) : null, [tableData])
|
|
||||||
|
|
||||||
const radius = useMemo(() => Math.min(width, height) / 2, [width, height])
|
|
||||||
|
|
||||||
useEffect(update, [])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!pieData) return
|
|
||||||
const slices = d3.select(svgRef)
|
|
||||||
.select('.slices')
|
|
||||||
.selectAll('path')
|
|
||||||
.data(pieData)
|
|
||||||
|
|
||||||
slices.exit().remove()
|
|
||||||
const newSlices = slices.enter().append('path')
|
|
||||||
|
|
||||||
slices.merge(newSlices)
|
|
||||||
.attr('class', 'tl-pie-part')
|
|
||||||
.attr('d', d3.arc().innerRadius(radius * 0.4).outerRadius(radius * 0.8))
|
|
||||||
.attr('fill', (d) => d.data.color)
|
|
||||||
.attr('data-id', (d) => d.idFeedRegulator)
|
|
||||||
.on('mouseover', onPieOver)
|
|
||||||
.on('mouseout', onPieOut)
|
|
||||||
}, [svgRef, pieData, radius, onPieOver, onPieOut])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!pieData) return
|
|
||||||
const innerArc = d3.arc().innerRadius(radius * 0.4).outerRadius(radius * 0.8)
|
|
||||||
const outerArc = d3.arc().innerRadius(radius * 0.9).outerRadius(radius * 0.9)
|
|
||||||
|
|
||||||
const lines = d3.select(svgRef)
|
|
||||||
.select('.lines')
|
|
||||||
.selectAll('polyline')
|
|
||||||
.data(pieData, (d) => d.data.nameFeedRegulator)
|
|
||||||
|
|
||||||
lines.exit().remove()
|
|
||||||
const newLines = lines.enter()
|
|
||||||
.append('polyline')
|
|
||||||
|
|
||||||
const abovePi = (d) => (d.startAngle + d.endAngle) / 2 < Math.PI
|
|
||||||
|
|
||||||
lines.merge(newLines)
|
|
||||||
.style('display', (d) => d.data.idFeedRegulator !== selectedRegulator ? 'none' : 'block')
|
|
||||||
.attr('points', (d) => {
|
|
||||||
const pos = outerArc.centroid(d)
|
|
||||||
pos[0] = radius * 0.95 * (abovePi(d) ? 1 : -1)
|
|
||||||
return [innerArc.centroid(d), outerArc.centroid(d), pos]
|
|
||||||
})
|
|
||||||
|
|
||||||
const lables = d3.select(svgRef)
|
|
||||||
.select('.labels')
|
|
||||||
.selectAll('text')
|
|
||||||
.data(pieData, (d) => d.data.nameFeedRegulator)
|
|
||||||
|
|
||||||
lables.exit().remove()
|
|
||||||
const newLabels = lables.enter()
|
|
||||||
.append('text')
|
|
||||||
.attr('dy', '.35em')
|
|
||||||
|
|
||||||
lables.merge(newLabels)
|
|
||||||
.attr('transform', (d) => {
|
|
||||||
const pos = outerArc.centroid(d)
|
|
||||||
pos[0] = radius * 0.95 * (abovePi(d) ? 1 : -1)
|
|
||||||
return `translate(${pos})`
|
|
||||||
})
|
|
||||||
.style('text-anchor', (d) => abovePi(d) ? 'start' : 'end')
|
|
||||||
.style('display', (d) => d.data.idFeedRegulator !== selectedRegulator ? 'none' : 'block')
|
|
||||||
.attr('width', radius * 0.4)
|
|
||||||
.text((d) => `${d.data.percent.toFixed(2)}% (${d.data.totalMinutes.toFixed(2)} мин)`)
|
|
||||||
|
|
||||||
}, [svgRef, pieData, radius, selectedRegulator])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Button onClick={() => setIsOpen(true)}>Статистика использования уставок</Button>
|
|
||||||
<Modal
|
|
||||||
centered
|
|
||||||
width={1024}
|
|
||||||
footer={false}
|
|
||||||
title={'Статистика использования уставок'}
|
|
||||||
onCancel={() => setIsOpen(false)}
|
|
||||||
open={isOpen}
|
|
||||||
>
|
|
||||||
<LoaderPortal show={isLoading}>
|
|
||||||
<div className={'filter-groups'}>
|
|
||||||
<Input.Group compact style={{ flex: 1 }}>
|
|
||||||
<Input
|
|
||||||
addonBefore={(
|
|
||||||
<Radio
|
|
||||||
checked={mode === 'depth'}
|
|
||||||
onChange={() => setMode('depth')}
|
|
||||||
>
|
|
||||||
По глубине
|
|
||||||
</Radio>
|
|
||||||
)}
|
|
||||||
allowClear
|
|
||||||
disabled={mode !== 'depth'}
|
|
||||||
prefix={'От'}
|
|
||||||
suffix={'м'}
|
|
||||||
style={{ width: 'calc(50% + 113px / 2)', textAlign: 'right' }}
|
|
||||||
onChange={(e) => onDepthChanged(e, 'from')}
|
|
||||||
value={depthFilter.from}
|
|
||||||
/>
|
|
||||||
<Input
|
|
||||||
allowClear
|
|
||||||
disabled={mode !== 'depth'}
|
|
||||||
prefix={'До'}
|
|
||||||
suffix={'м'}
|
|
||||||
style={{ width: 'calc(50% - 113px / 2)', textAlign: 'right' }}
|
|
||||||
onChange={(e) => onDepthChanged(e, 'to')}
|
|
||||||
value={depthFilter.to}
|
|
||||||
/>
|
|
||||||
</Input.Group>
|
|
||||||
<Input.Group compact style={{ flex: 1 }}>
|
|
||||||
<Input style={{ width: 128 }} addonBefore={(
|
|
||||||
<Radio
|
|
||||||
checked={mode === 'time'}
|
|
||||||
onChange={() => setMode('time')}
|
|
||||||
>
|
|
||||||
По времени
|
|
||||||
</Radio>
|
|
||||||
)}/>
|
|
||||||
<DateRangeWrapper
|
|
||||||
showTime
|
|
||||||
value={dateFilter}
|
|
||||||
disabled={mode !== 'time'}
|
|
||||||
onCalendarChange={setDateFilter}
|
|
||||||
disabledDate={(date) => date.isAfter(moment())}
|
|
||||||
/>
|
|
||||||
</Input.Group>
|
|
||||||
</div>
|
|
||||||
<Button onClick={update} style={{ marginTop: 10 }}>Обновить</Button>
|
|
||||||
<div className={'lps-pie-chart'} ref={ref}>
|
|
||||||
{data ? (
|
|
||||||
<svg ref={setSvgRef} width={'100%'} height={'100%'}>
|
|
||||||
<g transform={`translate(${width / 2}, ${height / 2})`}>
|
|
||||||
<g className={'slices'} stroke={'#0005'} />
|
|
||||||
<g className={'labels'} fill={'black'} />
|
|
||||||
<g className={'lines'} fill={'none'} stroke={'black'} />
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
) : (
|
|
||||||
<div className={'empty-wrapper'}>
|
|
||||||
<Empty />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className={'modal-label'}>Итоговая таблица по скважине</div>
|
|
||||||
<Table
|
|
||||||
bordered
|
|
||||||
size={'small'}
|
|
||||||
pagination={false}
|
|
||||||
dataSource={tableData}
|
|
||||||
columns={columns}
|
|
||||||
onRow={onRow}
|
|
||||||
/>
|
|
||||||
</LoaderPortal>
|
|
||||||
</Modal>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
export default LimitingParameterStatistics
|
|
@ -20,7 +20,6 @@ import {
|
|||||||
|
|
||||||
import { makeChartGroups, yAxis } from './dataset'
|
import { makeChartGroups, yAxis } from './dataset'
|
||||||
import { ADDITIVE_PAGES, cutData, DATA_COUNT, getLoadingInterval, makeDateTimeDisabled } from './archive_methods'
|
import { ADDITIVE_PAGES, cutData, DATA_COUNT, getLoadingInterval, makeDateTimeDisabled } from './archive_methods'
|
||||||
import LimitingParameterStatistics from './LimitingParameterStatistics'
|
|
||||||
import ActiveMessagesOnline from './ActiveMessagesOnline'
|
import ActiveMessagesOnline from './ActiveMessagesOnline'
|
||||||
import TelemetrySummary from './TelemetrySummary'
|
import TelemetrySummary from './TelemetrySummary'
|
||||||
import WirelineRunOut from './WirelineRunOut'
|
import WirelineRunOut from './WirelineRunOut'
|
||||||
@ -34,6 +33,7 @@ import SpinPicDisabled from '@images/SpinDisabled.png'
|
|||||||
|
|
||||||
import '@styles/pages/telemetry_view.less'
|
import '@styles/pages/telemetry_view.less'
|
||||||
import '@styles/pages/message.less'
|
import '@styles/pages/message.less'
|
||||||
|
import { useLiveLog } from '@asb/components/LiveLog'
|
||||||
|
|
||||||
const { Option } = Select
|
const { Option } = Select
|
||||||
|
|
||||||
@ -91,6 +91,8 @@ const TelemetryView = memo(() => {
|
|||||||
|
|
||||||
const [archiveMode, setArchiveMode] = useState(false)
|
const [archiveMode, setArchiveMode] = useState(false)
|
||||||
|
|
||||||
|
const log = useLiveLog()
|
||||||
|
|
||||||
const onStatusChanged = useCallback((value) => updateWell({ idState: value }), [well])
|
const onStatusChanged = useCallback((value) => updateWell({ idState: value }), [well])
|
||||||
|
|
||||||
const handleDataSaub = useCallback((data, replace = false) => {
|
const handleDataSaub = useCallback((data, replace = false) => {
|
||||||
@ -117,6 +119,7 @@ const TelemetryView = memo(() => {
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const onWheel = useCallback((e) => {
|
const onWheel = useCallback((e) => {
|
||||||
|
log('Wheel count', (prev) => prev ? prev + 1 : 1, 'warn')
|
||||||
if (!archiveMode && e.deltaY < 0) {
|
if (!archiveMode && e.deltaY < 0) {
|
||||||
setArchiveMode(true)
|
setArchiveMode(true)
|
||||||
} else if (archiveMode) {
|
} else if (archiveMode) {
|
||||||
@ -266,7 +269,6 @@ const TelemetryView = memo(() => {
|
|||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
<Setpoints />
|
<Setpoints />
|
||||||
<LimitingParameterStatistics />
|
|
||||||
<WirelineRunOut />
|
<WirelineRunOut />
|
||||||
<div className={'icons'}>
|
<div className={'icons'}>
|
||||||
<img src={isTorqueStabEnabled(spinLast) ? MomentStabPicEnabled : MomentStabPicDisabled} alt={'TorqueMaster'} />
|
<img src={isTorqueStabEnabled(spinLast) ? MomentStabPicEnabled : MomentStabPicDisabled} alt={'TorqueMaster'} />
|
||||||
|
@ -3,6 +3,7 @@ import {
|
|||||||
AlertOutlined,
|
AlertOutlined,
|
||||||
BarChartOutlined,
|
BarChartOutlined,
|
||||||
BuildOutlined,
|
BuildOutlined,
|
||||||
|
ControlOutlined,
|
||||||
DeploymentUnitOutlined,
|
DeploymentUnitOutlined,
|
||||||
ExperimentOutlined,
|
ExperimentOutlined,
|
||||||
FilePdfOutlined,
|
FilePdfOutlined,
|
||||||
@ -16,7 +17,7 @@ import { makeItem, PrivateMenu } from '@components/PrivateMenu'
|
|||||||
|
|
||||||
export const menuItems = [
|
export const menuItems = [
|
||||||
makeItem('Телеметрия', 'telemetry', [], <FundViewOutlined />, [
|
makeItem('Телеметрия', 'telemetry', [], <FundViewOutlined />, [
|
||||||
makeItem('Мониторинг', 'monitoring', [], <FundViewOutlined />),
|
makeItem('Мониторинг', 'telemetry', [], <FundViewOutlined />),
|
||||||
makeItem('Сообщения', 'messages', [], <AlertOutlined />),
|
makeItem('Сообщения', 'messages', [], <AlertOutlined />),
|
||||||
makeItem('ННБ', 'dashboard_nnb', [], <FolderOutlined />),
|
makeItem('ННБ', 'dashboard_nnb', [], <FolderOutlined />),
|
||||||
makeItem('Операции', 'operations', [], <FolderOutlined />),
|
makeItem('Операции', 'operations', [], <FolderOutlined />),
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
import { useState, useEffect, memo, useMemo, useCallback, FC } from 'react'
|
import { useState, useEffect, memo, useMemo, useCallback } from 'react'
|
||||||
import { Button, Tooltip } from 'antd'
|
|
||||||
import { FileOutlined } from '@ant-design/icons'
|
|
||||||
|
|
||||||
import { useWell, useTopRightBlock } from '@asb/context'
|
import { useWell } from '@asb/context'
|
||||||
import {
|
import {
|
||||||
EditableTable,
|
EditableTable,
|
||||||
makeGroupColumn,
|
makeGroupColumn,
|
||||||
@ -13,30 +11,10 @@ import {
|
|||||||
makeSelectColumn,
|
makeSelectColumn,
|
||||||
} from '@components/Table'
|
} from '@components/Table'
|
||||||
import LoaderPortal from '@components/LoaderPortal'
|
import LoaderPortal from '@components/LoaderPortal'
|
||||||
import { invokeWebApiWrapperAsync, download } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { ProcessMapService, WellOperationService } from '@api'
|
import { ProcessMapService, WellOperationService } from '@api'
|
||||||
import { arrayOrDefault } from '@utils'
|
import { arrayOrDefault } from '@utils'
|
||||||
|
|
||||||
const style = { margin: 4 }
|
|
||||||
|
|
||||||
const ImportExportBar = memo(({ well: givenWell, disabled }) => {
|
|
||||||
const [wellContext] = useWell()
|
|
||||||
const well = useMemo(() => givenWell ?? wellContext, [givenWell, wellContext])
|
|
||||||
|
|
||||||
const downloadExport = useCallback(
|
|
||||||
async () => await download(`/api/ProcessMap/getReportFile/${well.id}`),
|
|
||||||
[well.id]
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<Tooltip title={'Выгрузка расширенной автоформируемой РТК'}>
|
|
||||||
<Button disabled={disabled} icon={<FileOutlined />} style={style} onClick={downloadExport} />
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
const numericRender = makeNumericRender(2)
|
const numericRender = makeNumericRender(2)
|
||||||
|
|
||||||
export const getColumns = async (idWell) => {
|
export const getColumns = async (idWell) => {
|
||||||
@ -70,7 +48,6 @@ export const DrillProcessFlow = memo(() => {
|
|||||||
const [columns, setColumns] = useState([])
|
const [columns, setColumns] = useState([])
|
||||||
|
|
||||||
const [well] = useWell()
|
const [well] = useWell()
|
||||||
const setTopRightBlock = useTopRightBlock()
|
|
||||||
|
|
||||||
const updateFlows = useCallback(() => invokeWebApiWrapperAsync(
|
const updateFlows = useCallback(() => invokeWebApiWrapperAsync(
|
||||||
async () => {
|
async () => {
|
||||||
@ -98,10 +75,6 @@ export const DrillProcessFlow = memo(() => {
|
|||||||
updateFlows()
|
updateFlows()
|
||||||
}, [well])
|
}, [well])
|
||||||
|
|
||||||
useEffect(() => setTopRightBlock((well) => (
|
|
||||||
<ImportExportBar well={well} />
|
|
||||||
)), [setTopRightBlock])
|
|
||||||
|
|
||||||
const tableHandlers = useMemo(() => {
|
const tableHandlers = useMemo(() => {
|
||||||
const handlerProps = {
|
const handlerProps = {
|
||||||
service: ProcessMapService,
|
service: ProcessMapService,
|
||||||
@ -115,12 +88,7 @@ export const DrillProcessFlow = memo(() => {
|
|||||||
return {
|
return {
|
||||||
add: { ...handlerProps, action: 'insert', actionName: 'Добавление месторождения', recordParser },
|
add: { ...handlerProps, action: 'insert', actionName: 'Добавление месторождения', recordParser },
|
||||||
edit: { ...handlerProps, action: 'update', actionName: 'Редактирование месторождения', recordParser },
|
edit: { ...handlerProps, action: 'update', actionName: 'Редактирование месторождения', recordParser },
|
||||||
delete: {
|
delete: { ...handlerProps, action: 'delete', actionName: 'Удаление месторождения', permission: 'DrillFlowChart.delete' },
|
||||||
...handlerProps,
|
|
||||||
action: 'delete',
|
|
||||||
actionName: 'Удаление месторождения',
|
|
||||||
permission: 'DrillFlowChart.delete',
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}, [updateFlows, well.id])
|
}, [updateFlows, well.id])
|
||||||
|
|
||||||
|
@ -6,6 +6,8 @@ import { useState, useEffect, memo, useMemo, useCallback } from 'react'
|
|||||||
import { useTopRightBlock, useWell } from '@asb/context'
|
import { useTopRightBlock, useWell } from '@asb/context'
|
||||||
import {
|
import {
|
||||||
EditableTable,
|
EditableTable,
|
||||||
|
makeColumn,
|
||||||
|
makeNumericColumnOptions,
|
||||||
makeSelectColumn,
|
makeSelectColumn,
|
||||||
makeDateColumn,
|
makeDateColumn,
|
||||||
makeNumericColumn,
|
makeNumericColumn,
|
||||||
@ -70,7 +72,8 @@ export const WellOperationsEditor = memo(({ idType, showNpt, ...other }) => {
|
|||||||
const skip = ((pageNumAndPageSize.current - 1) * pageNumAndPageSize.pageSize) || 0
|
const skip = ((pageNumAndPageSize.current - 1) * pageNumAndPageSize.pageSize) || 0
|
||||||
const take = pageNumAndPageSize.pageSize
|
const take = pageNumAndPageSize.pageSize
|
||||||
const paginatedOperations = await WellOperationService.getOperations(well.id,
|
const paginatedOperations = await WellOperationService.getOperations(well.id,
|
||||||
undefined, undefined, undefined, undefined, undefined, idType, undefined, skip, take)
|
idType, undefined, undefined, undefined, undefined,
|
||||||
|
undefined, undefined, skip, take)
|
||||||
const operations = paginatedOperations?.items ?? []
|
const operations = paginatedOperations?.items ?? []
|
||||||
setOperations(operations)
|
setOperations(operations)
|
||||||
const total = paginatedOperations.count?? paginatedOperations.items?.length ?? 0
|
const total = paginatedOperations.count?? paginatedOperations.items?.length ?? 0
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { memo, useEffect, useMemo, useState } from 'react'
|
import { memo, useEffect, useMemo, useState } from 'react'
|
||||||
|
import { useElementSize } from 'usehooks-ts'
|
||||||
import { Empty } from 'antd'
|
import { Empty } from 'antd'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import * as d3 from 'd3'
|
import * as d3 from 'd3'
|
||||||
@ -7,7 +8,7 @@ import LoaderPortal from '@components/LoaderPortal'
|
|||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { DetectedOperationService } from '@api'
|
import { DetectedOperationService } from '@api'
|
||||||
import { unique } from '@utils/filters'
|
import { unique } from '@utils/filters'
|
||||||
import { formatDate, useElementSize } from '@utils'
|
import { formatDate } from '@utils'
|
||||||
|
|
||||||
import { makeTooltipRender } from '../../Telemetry/Operations/OperationsChart'
|
import { makeTooltipRender } from '../../Telemetry/Operations/OperationsChart'
|
||||||
import { makeGetColor } from '.'
|
import { makeGetColor } from '.'
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
|
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
|
import { useElementSize } from 'usehooks-ts'
|
||||||
import { Empty } from 'antd'
|
import { Empty } from 'antd'
|
||||||
import * as d3 from 'd3'
|
import * as d3 from 'd3'
|
||||||
|
|
||||||
@ -7,7 +8,6 @@ import LoaderPortal from '@components/LoaderPortal'
|
|||||||
import { invokeWebApiWrapperAsync } from '@components/factory'
|
import { invokeWebApiWrapperAsync } from '@components/factory'
|
||||||
import { DetectedOperationService } from '@api'
|
import { DetectedOperationService } from '@api'
|
||||||
import { unique } from '@utils/filters'
|
import { unique } from '@utils/filters'
|
||||||
import { useElementSize } from '@utils'
|
|
||||||
|
|
||||||
import { makeGetColor } from '.'
|
import { makeGetColor } from '.'
|
||||||
|
|
||||||
|
@ -95,11 +95,10 @@ const Well = memo(() => {
|
|||||||
<Route path={'*'} element={<NoAccessComponent />} />
|
<Route path={'*'} element={<NoAccessComponent />} />
|
||||||
|
|
||||||
<Route path={'telemetry/*'} element={<Telemetry />}>
|
<Route path={'telemetry/*'} element={<Telemetry />}>
|
||||||
<Route index element={<Navigate to={'monitoring'} replace />} />
|
<Route index element={<Navigate to={'telemetry'} replace />} />
|
||||||
<Route path={'*'} element={<NoAccessComponent />} />
|
<Route path={'*'} element={<NoAccessComponent />} />
|
||||||
<Route path={'telemetry'} element={<Navigate to={'../monitoring'} replace />} /> {/* TODO: Remove in next release */}
|
|
||||||
|
|
||||||
<Route path={'monitoring'} element={<TelemetryView />} />
|
<Route path={'telemetry'} element={<TelemetryView />} />
|
||||||
<Route path={'messages'} element={<Messages />} />
|
<Route path={'messages'} element={<Messages />} />
|
||||||
<Route path={'dashboard_nnb/*'} element={<DashboardNNB />} />
|
<Route path={'dashboard_nnb/*'} element={<DashboardNNB />} />
|
||||||
<Route path={'operations'} element={<Operations />} />
|
<Route path={'operations'} element={<Operations />} />
|
||||||
|
@ -11,7 +11,7 @@ export const AccessDenied = memo(() => {
|
|||||||
return (
|
return (
|
||||||
<Result
|
<Result
|
||||||
status={'error'}
|
status={'error'}
|
||||||
title={'Доступ запрещён'}
|
title={'Доступ запрешён'}
|
||||||
subTitle={'Страницы не существует или у вас отсутствует к ней доступ.'}
|
subTitle={'Страницы не существует или у вас отсутствует к ней доступ.'}
|
||||||
>
|
>
|
||||||
<div className={'desc'}>
|
<div className={'desc'}>
|
||||||
|
53
src/styles/components/live_log.less
Normal file
53
src/styles/components/live_log.less
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
.log-box {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 1000;
|
||||||
|
background-color: rgba(0,0,0,.5);
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 10px;
|
||||||
|
color: white;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
overflow-x: hidden;
|
||||||
|
width: 300px;
|
||||||
|
|
||||||
|
&.log-box-top-left { top: 10px; left: 10px; }
|
||||||
|
&.log-box-top-right { top: 10px; right: 10px; }
|
||||||
|
&.log-box-bottom-left { bottom: 10px; left: 10px; }
|
||||||
|
&.log-box-bottom-right { bottom: 10px; right: 10px; }
|
||||||
|
|
||||||
|
& .log-box-label {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
& span {
|
||||||
|
font-size: 1.25em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& .log-box-content {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: 5px;
|
||||||
|
|
||||||
|
& .log-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
|
||||||
|
& .log-close {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .log-name {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.log-info { color: white }
|
||||||
|
&.log-warn { color: #faad14 }
|
||||||
|
&.log-error { color: #ff4d4f }
|
||||||
|
&.log-critical { color: #ff4d4f; font-weight: bolder; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,29 +0,0 @@
|
|||||||
.filter-groups {
|
|
||||||
display: flex;
|
|
||||||
gap: 10px;
|
|
||||||
|
|
||||||
& .filter-label {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
& .date-filter {
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-label {
|
|
||||||
width: 100%;
|
|
||||||
margin: 20px 0;
|
|
||||||
font-size: 1rem;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.lps-pie-chart {
|
|
||||||
min-height: 30vh;
|
|
||||||
max-height: 50vh;
|
|
||||||
}
|
|
@ -1,5 +1,4 @@
|
|||||||
export * from './cachedFetch'
|
export * from './cachedFetch'
|
||||||
export * from './functionalValue'
|
export * from './functionalValue'
|
||||||
export * from './useElementSize'
|
|
||||||
export * from './usePartialProps'
|
export * from './usePartialProps'
|
||||||
export * from './useUserSettings'
|
export * from './useUserSettings'
|
||||||
|
@ -1,24 +0,0 @@
|
|||||||
import { MutableRefObject, useEffect, useMemo, useRef, useState } from 'react'
|
|
||||||
|
|
||||||
export const useElementSize = <T extends Element>(): [MutableRefObject<T | null>, DOMRectReadOnly] => {
|
|
||||||
const ref = useRef<T>(null)
|
|
||||||
const [rect, setRect] = useState<DOMRectReadOnly>(new DOMRect())
|
|
||||||
|
|
||||||
const observer = useMemo(() => new ResizeObserver((entries) => {
|
|
||||||
if (entries.length <= 0) return
|
|
||||||
const rect = entries[0].contentRect
|
|
||||||
setRect(rect)
|
|
||||||
}), [])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!ref.current) return
|
|
||||||
observer.observe(ref.current)
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
if (!ref.current) return
|
|
||||||
observer.unobserve(ref.current)
|
|
||||||
}
|
|
||||||
}, [ref.current, observer])
|
|
||||||
|
|
||||||
return [ref, rect]
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user