bug fix and add dockerfile (#9)
diff --git a/README.md b/README.md
index b208008..ba9b960 100644
--- a/README.md
+++ b/README.md
@@ -20,17 +20,21 @@
-->
# iotdb-tsfile-viewer
+<!--
[![Main Mac and Linux](https://github.com/apache/iotdb/actions/workflows/main-unix.yml/badge.svg)](https://github.com/apache/iotdb/actions/workflows/main-unix.yml)
[![Main Win](https://github.com/apache/iotdb/actions/workflows/main-win.yml/badge.svg)](https://github.com/apache/iotdb/actions/workflows/main-win.yml)
+-->
# Outline
- [Introduction](#Introduction)
- [Quick Start](#quick-start)
- [Prerequisites](#Prerequisites)
- [Compile](#Compile)
- [User Guide](#user-guide)
+- [Build Docker Image](#build-docker-image)
- [Maintainers](#Maintainers)
- [Contributing](#Contributing)
- [Contributors](#Contributors)
+- [FAQ Summary](#faq-summary)
# Introduction
tsfile-viewer is a tool to view TSFILE. Currently, we support bit granularity parsing of TsFile and provide visual display.
we have three modules in the project
@@ -103,6 +107,14 @@
web:
baseDirectory: C:\Users\Administrator\Desktop\
```
+The system can load up to 5 tsfile files by default, you can modify this value through application.yml
+```
+tsfile-viewer-web\src\main\resources\application.yml
+
+tsviewer:
+ web:
+ containerSize: 5
+```
# User Guide
@@ -150,6 +162,16 @@
Data Search function:
![image](/imgs/datasearch.png)
+# Build Docker Image
+There is a dockerfile in tsfile-viewer-web, through which you can easily build a docker image.
+After you have successfully executed the 'mvn clean install' command,enter the tsfile-viewer-web project and execute the following command
+```
+docker build -t iotdb-tsfile-viewer:0.13.2-SNAPSHOT .
+docker run --volume=D:\tsfile:/tsfile -p 8080:8080 -d iotdb-tsfile-viewer:0.13.2-SNAPSHOT
+```
+
+docker.yml is the corresponding configuration file in docker image.
+If you want to modify the folder path of tsfile, you need to modify docker.yml, dockerfile and the --volume parameter of the dock run command.
# Maintainers
@@ -157,3 +179,7 @@
Feel free to dive in! Open an issue or submit PRs.
# Contributors
This project exists thanks to all the people who contribute.
+
+# FAQ Summary
+- 1 After the project is cloned, use mvn clean install to report a spotless exception; usually found on windows, the reason is that when git clones the project on windows, LF will be converted to CRLF, in this case, execute the mvn spotless:apply command to solve the problem
+- 2 Some front-end components cannot be downloaded, enter the tsfile-viewer-web-frontend project, enter the node folder, and execute the command ".\yarn\dist\bin\yarn set registry http://registry.npm.taobao.org/" switch to Taobao mirror
diff --git a/tsfile-viewer-web-frontend/config/config.js b/tsfile-viewer-web-frontend/config/config.js
index 1d49193..4608063 100644
--- a/tsfile-viewer-web-frontend/config/config.js
+++ b/tsfile-viewer-web-frontend/config/config.js
@@ -49,6 +49,10 @@
// umi routes: https://umijs.org/docs/routing
routes: [
{
+ path: '/',
+ component: 'Welcome'
+ },
+ {
path: '/tsfile-tool/v2/',
name: 'tsfile-tool',
icon: 'FileOutlined',
diff --git a/tsfile-viewer-web-frontend/pom.xml b/tsfile-viewer-web-frontend/pom.xml
index 34d9b9e..c3b5f63 100644
--- a/tsfile-viewer-web-frontend/pom.xml
+++ b/tsfile-viewer-web-frontend/pom.xml
@@ -57,6 +57,11 @@
<phase>generate-resources</phase>
</execution>
<execution>
+ <id></id>
+ <goals></goals>
+ <phase></phase>
+ </execution>
+ <execution>
<id>yarn install</id>
<goals>
<goal>yarn</goal>
diff --git a/tsfile-viewer-web-frontend/src/app.jsx b/tsfile-viewer-web-frontend/src/app.jsx
index 8800132..d48ac05 100644
--- a/tsfile-viewer-web-frontend/src/app.jsx
+++ b/tsfile-viewer-web-frontend/src/app.jsx
@@ -16,10 +16,10 @@
*/
import { PageLoading } from '@ant-design/pro-layout';
import { message } from 'antd';
-import { history,SelectLang } from 'umi';
+import { history, SelectLang } from 'umi';
import Footer from '@/components/Footer';
import { forwardRef } from 'react';
-const fowardPath = '/tsfile-tool/v2/';
+const fowardPath = '/';
/** 获取用户信息比较慢的时候会展示一个 loading */
export const initialStateConfig = {
@@ -67,10 +67,10 @@
export const request = {
errorHandler: (error) => {
const { response } = error;
- if(response.status == '404'){
+ if (response.status == '404') {
history.push("/exception/404")
}
- if(response.status == '500' || response.status == '502' || response.status == '503' || response.status == '504'){
+ if (response.status == '500' || response.status == '502' || response.status == '503' || response.status == '504') {
history.push("/exception/500")
}
throw error;
@@ -85,15 +85,15 @@
onPageChange: () => {
const { location } = history; // 如果没有登录,重定向到 login
let pathname = location.pathname.endsWith('/') ?
- location.pathname.substring(0, location.pathname.length - 1):
- location.pathname;
- if(pathname == ''){
+ location.pathname.substring(0, location.pathname.length - 1) :
+ location.pathname;
+ if (pathname == '') {
history.push(fowardPath);
}
},
menuHeaderRender: undefined,
itemRender: (route, params, routes) => {
- if(routes.indexOf(route) == route.length -1){
+ if (routes.indexOf(route) == route.length - 1) {
return <span>{route.breadcrumbName}</span>;
}
return <span>{route.breadcrumbName}</span>;
diff --git a/tsfile-viewer-web-frontend/src/locales/en-US.js b/tsfile-viewer-web-frontend/src/locales/en-US.js
index ff117bd..6e5e636 100644
--- a/tsfile-viewer-web-frontend/src/locales/en-US.js
+++ b/tsfile-viewer-web-frontend/src/locales/en-US.js
@@ -26,6 +26,17 @@
'app.preview.down.block': 'Download this page to your local project',
'app.welcome.link.fetch-blocks': 'Get all block',
'app.welcome.link.block-list': 'Quickly build standard, pages based on `block` development',
+ 'pages.welcome.message':'Welcome to tsfile viewer',
+ 'pages.welcome.alertMessage':'The front-end project build depends on antd and antdpro',
+ 'pages.welcome.antd':'Antd URL',
+ 'pages.welcome.antdpro':'Antd pro URL',
+ 'pages.welcome.git':'The git address of this project',
+ 'overview.explanation': 'explanation',
+ 'overview.magicNumber': 'magic number',
+ 'overview.version': 'version',
+ 'overview.endExplanation': 'The character TSFILE at the end of the file, marking the end of the file,',
+ 'overview.structureDescription': 'Structure Description',
+ 'overview.details': 'Details',
...menu,
...pwa,
...tsviewer,
diff --git a/tsfile-viewer-web-frontend/src/locales/en-US/menu.js b/tsfile-viewer-web-frontend/src/locales/en-US/menu.js
index c7a9093..447196c 100644
--- a/tsfile-viewer-web-frontend/src/locales/en-US/menu.js
+++ b/tsfile-viewer-web-frontend/src/locales/en-US/menu.js
@@ -15,7 +15,7 @@
* limitations under the License.
*/
export default {
- 'menu.platform.file-preview': 'File Preview',
+ 'menu.tsfile-tool': 'Tsfile viewer',
'menu.exception.403': '403',
'menu.exception.404': '404',
'menu.exception.500': '500',
diff --git a/tsfile-viewer-web-frontend/src/locales/zh-CN.js b/tsfile-viewer-web-frontend/src/locales/zh-CN.js
index 3c18f01..6e4fcea 100644
--- a/tsfile-viewer-web-frontend/src/locales/zh-CN.js
+++ b/tsfile-viewer-web-frontend/src/locales/zh-CN.js
@@ -26,6 +26,17 @@
'app.preview.down.block': '下载此页面到本地项目',
'app.welcome.link.fetch-blocks': '获取全部区块',
'app.welcome.link.block-list': '基于 block 开发,快速构建标准页面',
+ 'pages.welcome.message':'欢迎使用tsfile viewer',
+ 'pages.welcome.alertMessage':'前端项目构建依赖于antd和antdpro。',
+ 'pages.welcome.antd':'antd 网址',
+ 'pages.welcome.antdpro':'antd pro 网址',
+ 'pages.welcome.git':'本项目的git地址',
+ 'overview.explanation': '说明',
+ 'overview.magicNumber': '魔数',
+ 'overview.version': '版本',
+ 'overview.endExplanation': '文件末尾的字符TSFILE,标记文件结束,',
+ 'overview.structureDescription': '结构说明',
+ 'overview.details': '内容详情',
...menu,
...pwa,
...tsviewer,
diff --git a/tsfile-viewer-web-frontend/src/pages/Welcome.jsx b/tsfile-viewer-web-frontend/src/pages/Welcome.jsx
index 5cfad7c..ad61577 100644
--- a/tsfile-viewer-web-frontend/src/pages/Welcome.jsx
+++ b/tsfile-viewer-web-frontend/src/pages/Welcome.jsx
@@ -31,12 +31,25 @@
export default () => {
const intl = useIntl();
return (
- <PageContainer>
+ <PageContainer
+ fixedHeader
+ header={{
+ title: (
+ <div style={{ whiteSpace: "pre-wrap" }}>
+ {intl.formatMessage({
+ id: 'pages.welcome.message',
+ defaultMessage: 'Welcome to Tsfile viewer',
+ })}
+ </div>
+ ),
+ ghost: true,
+ }}
+ >
<Card>
<Alert
message={intl.formatMessage({
id: 'pages.welcome.alertMessage',
- defaultMessage: 'Faster and stronger heavy-duty components have been released.',
+ defaultMessage: 'The front-end project build depends on antd and antdpro',
})}
type="success"
showIcon
@@ -47,32 +60,33 @@
}}
/>
<Typography.Text strong>
- <FormattedMessage id="pages.welcome.advancedComponent" defaultMessage="Advanced Form" />{' '}
- <a
- href="https://procomponents.ant.design/components/table"
- rel="noopener noreferrer"
- target="__blank"
- >
- <FormattedMessage id="pages.welcome.link" defaultMessage="Welcome" />
- </a>
+ <FormattedMessage id="pages.welcome.antd" defaultMessage={intl.formatMessage({
+ id: 'pages.welcome.antd',
+ defaultMessage: 'antd URL',
+ })} />{' '}
</Typography.Text>
- <CodePreview>yarn add @ant-design/pro-table</CodePreview>
+ <CodePreview>https://ant.design/index-cn</CodePreview>
+
+ <Typography.Text strong>
+ <FormattedMessage id="pages.welcome.antdpro" defaultMessage={intl.formatMessage({
+ id: 'pages.welcome.antdpro',
+ defaultMessage: ' antd pro URL',
+ })} />{' '}
+ </Typography.Text>
+ <CodePreview>https://pro.ant.design/zh-CN/</CodePreview>
+
<Typography.Text
strong
style={{
marginBottom: 12,
}}
>
- <FormattedMessage id="pages.welcome.advancedLayout" defaultMessage="Advanced layout" />{' '}
- <a
- href="https://procomponents.ant.design/components/layout"
- rel="noopener noreferrer"
- target="__blank"
- >
- <FormattedMessage id="pages.welcome.link" defaultMessage="Welcome" />
- </a>
+ <FormattedMessage id="pages.welcome.git" defaultMessage={intl.formatMessage({
+ id: 'pages.welcome.git',
+ defaultMessage: 'The git address of this project',
+ })} />{' '}
</Typography.Text>
- <CodePreview>yarn add @ant-design/pro-layout</CodePreview>
+ <CodePreview>https://github.com/apache/iotdb-tsfile-viewer</CodePreview>
</Card>
</PageContainer>
);
diff --git a/tsfile-viewer-web-frontend/src/pages/tsfile-tool/v2/fileSelect/fileSelect.jsx b/tsfile-viewer-web-frontend/src/pages/tsfile-tool/v2/fileSelect/fileSelect.jsx
index 18f5eb1..e6c7dfa 100644
--- a/tsfile-viewer-web-frontend/src/pages/tsfile-tool/v2/fileSelect/fileSelect.jsx
+++ b/tsfile-viewer-web-frontend/src/pages/tsfile-tool/v2/fileSelect/fileSelect.jsx
@@ -82,6 +82,7 @@
filters: [
{ text: intl.formatMessage({ id: 'tsviewer.fileSelect.loaded', }), value: "LOADED" },
],
+ filteredValue: tableFilters == undefined ? null : tableFilters.status || null,
render(text, record, index) {
if (record.status == 'EXCLUDED') {
return
@@ -155,6 +156,7 @@
setBreadData(arr.map((item, key) => {
return <><div style={{ cursor: "pointer", background: "#f7e0e0" }}><Breadcrumb.Item key={key} onClick={() => { openDrictory(generateBreadArray(arr, key)) }}>{item}</Breadcrumb.Item></div><Breadcrumb.Separator>{'>'}</Breadcrumb.Separator></>;
}))
+ setTableFilters();
} else {
notification.error({
message: res.message,
@@ -275,6 +277,7 @@
}
const handelTableChanged = async (pagenation, filters) => {
+ console.log(filters)
setTableFilters(filters)
setIsSameFolder(false)
}
diff --git a/tsfile-viewer-web-frontend/src/pages/tsfile-tool/v2/overview/moreChunkGroup.jsx b/tsfile-viewer-web-frontend/src/pages/tsfile-tool/v2/overview/moreChunkGroup.jsx
index 6dd90f1..3139b0f 100644
--- a/tsfile-viewer-web-frontend/src/pages/tsfile-tool/v2/overview/moreChunkGroup.jsx
+++ b/tsfile-viewer-web-frontend/src/pages/tsfile-tool/v2/overview/moreChunkGroup.jsx
@@ -17,13 +17,14 @@
import React, { useState, useEffect } from "react";
import { Card, Layout, Button, Drawer, Tree, Input, PageHeader, notification, Pagination, Table, Tooltip } from 'antd';
import styles from '../style.less'
-import { GroupOutlined, RetweetOutlined, CopyOutlined, QuestionCircleOutlined } from '@ant-design/icons';
+import { GroupOutlined, RetweetOutlined, MinusSquareOutlined, PlusSquareOutlined, CopyOutlined, QuestionCircleOutlined } from '@ant-design/icons';
import {
getChunkGroupsListUsingPOST, getChunkGroupInfoUsingPOST, getChunkListUsingPOST, getPageListUsingPOST
, getPageInfoUsingPOST
} from '@/services/swagger1/tsfileViewerController'
import moment from 'moment';
import { useIntl } from 'umi';
+import { Tree as TreeArborist } from "react-arborist";
const { Header, Footer, Sider, Content } = Layout;
const { Search } = Input;
@@ -42,6 +43,9 @@
const [columnsLength, setColumnsLength] = useState();
const [pageData, setPageData] = useState()
const intl = useIntl();
+ const [structureMapLoading, setStructureMapLoading] = useState();
+ const [treeHeight, setTreeHeight] = useState()
+ const [randomFlag, setRandomFlag] = useState()
var pageDataCache;
@@ -70,17 +74,24 @@
if (details == undefined) {
return
}
+ setStructureMapLoading(true)
let res = await getChunkListUsingPOST({ offset: details.offset, filePath: filePath, offsetType: 'CG' })
if (res.code == 0) {
- setTreeData(Object.values(res.data).map((chunkInfo) => {
+ setRandomFlag(moment(new Date()).valueOf())
+ let tree = Object.values(res.data).map((node) => {
let tree = {};
- tree['title'] = chunkInfo.measurementId;
- tree['key'] = chunkInfo.offset;
- tree['icon'] = <GroupOutlined />
+ tree['name'] = node.measurementId;
+ tree['id'] = node.measurementId + '-' + node.offset + randomFlag;
+ // tree['icon'] = <RightOutlined />
tree['isLeaf'] = false;
+ tree['position'] = node.offset;
return tree;
- }))
+ })
+ setTreeData(tree)
setopenChunk(true);
+ var div = document.getElementById('tree-div');
+ setTreeHeight(div.offsetHeight)
+ setStructureMapLoading(false);
} else {
notification.error({
message: res.message,
@@ -89,50 +100,6 @@
};
- const updateTreeData = (list, key, children) => {
- return list.map((node) => {
- if (node.key === key) {
- return {
- ...node,
- children,
- };
- }
- // 这个应该是用来加载子节点的子节点的,应该需要修改children对象
- // if (node.children) {
- // return {
- // ...node,
- // children: updateTreeData(node.children, key, children),
- // };
- // }
- return node;
- });
- }
-
-
- const onLoadData = async ({ key, children }) => {
- let res = await getPageListUsingPOST({ offset: key, filePath: filePath })
-
- if (res.code == 0) {
- let pages = Object.values(res.data).map((pageInfo) => {
- let tree = {};
- tree['title'] = pageInfo.pageNo;
- tree['key'] = pageInfo.offset;
- tree['icon'] = <CopyOutlined />
- tree['isLeaf'] = true;
- tree['pageInfo'] = pageInfo;
- return tree;
- })
- setTreeData(origin =>
- updateTreeData(origin, key, pages),
- );
- } else {
- notification.error({
- message: res.message,
- });
- }
-
- }
-
const onCloseChunk = () => {
setopenChunk(false);
};
@@ -145,55 +112,57 @@
setopenPage(false);
};
- const onSelect = async (selectedKeys, info) => {
- if (info.node.isLeaf == true) {
- let param = info.node.pageInfo;
- param['chunkGroupOffset'] = details.offset;
- param['filePath'] = filePath;
- let res = await getPageInfoUsingPOST(param)
- if (res.code == 0) {
- //pagedata信息
- let cols = [{
- title: 'No',
- fixed: 'left',
- width: '100px',
- // render: (text, record, index) => `${index + 1}`, //每一页都从1开始
- render: (text, record, index) => {
- return index + 1
- }
+ const onSelect = async (info) => {
+ if (!info.isLeaf) {
+ return
+ }
+ let param = info.pageInfo;
+ param['chunkGroupOffset'] = details.offset;
+ param['filePath'] = filePath;
+ let res = await getPageInfoUsingPOST(param)
+ if (res.code == 0) {
+ //pagedata信息
+ let cols = [{
+ title: 'No',
+ fixed: 'left',
+ width: '100px',
+ // render: (text, record, index) => `${index + 1}`, //每一页都从1开始
+ render: (text, record, index) => {
+ return index + 1
+ }
- }]
- cols.push(...Object.values(res.data.title).map((titleName, key) => {
- if (titleName == 'timestamp') {
- return {
- title: (
+ }]
+ cols.push(...Object.values(res.data.title).map((titleName, key) => {
+ if (titleName == 'timestamp') {
+ return {
+ title: (
+ <>
+ {titleName}<span>{'\u00A0\u00A0\u00A0\u00A0'}</span>
+ <RetweetOutlined
+ onClick={() => {
+ pageDataCache = Object.values(pageDataCache).map((item) => {
+ if ((item[0] + "").indexOf("-") > -1) {
+ item[0] = moment(item[0], 'YYYY-MM-DD HH:mm:ss.SSS').valueOf()
+ } else {
+ item[0] = moment(Number(item[0])).format('YYYY-MM-DD HH:mm:ss.SSS')
+ }
+ return item
+ })
+ setPageData(pageDataCache)
+ }}
+ />
+ </>),
+ dataIndex: titleName,
+ key: titleName,
+ fixed: 'left',
+ width: '250px',
+ render: (text, record, index) => {
+ return (
<>
- {titleName}<span>{'\u00A0\u00A0\u00A0\u00A0'}</span>
- <RetweetOutlined
- onClick={() => {
- pageDataCache = Object.values(pageDataCache).map((item)=>{
- if((item[0]+"").indexOf("-") > -1){
- item[0] = moment(item[0],'YYYY-MM-DD HH:mm:ss.SSS').valueOf()
- } else {
- item[0] = moment(Number(item[0])).format('YYYY-MM-DD HH:mm:ss.SSS')
- }
- return item
- })
- setPageData(pageDataCache)
- }}
- />
- </>),
- dataIndex: titleName,
- key: titleName,
- fixed: 'left',
- width: '250px',
- render: (text, record, index) => {
- return (
- <>
- <span id={index}>
- {record[key]}
- </span>
- {/* <span>{'\u00A0\u00A0\u00A0\u00A0'}</span>
+ <span id={index}>
+ {record[key]}
+ </span>
+ {/* <span>{'\u00A0\u00A0\u00A0\u00A0'}</span>
<RetweetOutlined
onClick={(e) => {
if (document.getElementById(index).innerText.indexOf("-") > -1) {
@@ -202,33 +171,32 @@
document.getElementById(index).innerText = moment(Number(record[key])).format('YYYY-MM-DD HH:mm:ss.SSS')
}
}} /> */}
- </>
- )
+ </>
+ )
- // return moment(Number(record[key])).format('YYYY-MM-DD HH:mm:ss.SSS')
- }
- }
- } else {
- return {
- title: titleName,
- dataIndex: titleName,
- key: titleName,
- render: (text, record, index) => {
- return record[key]
- }
+ // return moment(Number(record[key])).format('YYYY-MM-DD HH:mm:ss.SSS')
}
}
- }))
- setColumnsLength(cols.length)
- setColumns(cols)
- setPageData(res.data.values);
- pageDataCache = res.data.values;
- showPage()
- } else {
- notification.error({
- message: res.message,
- });
- }
+ } else {
+ return {
+ title: titleName,
+ dataIndex: titleName,
+ key: titleName,
+ render: (text, record, index) => {
+ return record[key]
+ }
+ }
+ }
+ }))
+ setColumnsLength(cols.length)
+ setColumns(cols)
+ setPageData(res.data.values);
+ pageDataCache = res.data.values;
+ showPage()
+ } else {
+ notification.error({
+ message: res.message,
+ });
}
};
@@ -293,6 +261,61 @@
cardSelect = id;
}
+ const updateTreeData = (list, id, children) => {
+ return list.map((node) => {
+ if (node.id === id) {
+ return {
+ ...node,
+ children,
+ };
+ }
+ //这个应该是用来加载子节点的子节点的,应该需要修改children对象
+ if (node.children) {
+ return {
+ ...node,
+ children: updateTreeData(node.children, id, children),
+ };
+ }
+ return node;
+ });
+ }
+
+ const onLoadTreeData = async (expanded, node) => {
+ if (expanded && node.children == undefined) {
+ let res = await getPageListUsingPOST({ offset: node.position, filePath: filePath })
+ if (res.code == 0) {
+ let newTree = Object.values(res.data).map((child) => {
+ let tree = {};
+ tree['name'] = child.pageNo;
+ tree['id'] = child.pageNo + '-' + child.offset + randomFlag;
+ tree['isLeaf'] = true;
+ tree['position'] = child.offset;
+ tree['pageInfo'] = child;
+ return tree;
+ })
+ let data = treeData;
+ let pa = updateTreeData(data, node.id, newTree);
+ setTreeData(pa)
+ } else {
+ notification.error({
+ message: res.message,
+ });
+ }
+ }
+ }
+
+ function Node({ node, style, dragHandle, tree }) {
+ if (!node.isOpen && !node.data.isLeaf && node.data.children == undefined && node.isSelected) {
+ onLoadTreeData(!node.isOpen, node.data)
+ }
+ /* This node instance can do many things. See the API reference. */
+ return (
+ <div style={{ ...style, overflow: "hidden", width: "155vh", textOverflow: "ellipsis", whiteSpace: "nowrap" }} ref={dragHandle} onClick={() => (onSelect(node.data))}>
+ {node.data.isLeaf ? "" : node.isOpen ? <MinusSquareOutlined onClick={() => (node.toggle())} /> : <PlusSquareOutlined onClick={() => (node.toggle())} />} <span style={{ background: node.isSelected && node.data.isLeaf ? "#FFDFD4" : "white" }}>{node.data.isLeaf ? <CopyOutlined /> : <GroupOutlined />}{node.data.name}</span>
+ </div>
+ );
+ }
+
useEffect(() => {
generateChunkGroupCards(1, pageSize);
}, [])
@@ -308,7 +331,7 @@
placeholder="deviceName"
allowClear
onChange={(e) => { setDeviceNameLike(e.target.value) }}
- onSearch={() => { generateChunkGroupCards(1, pageSize) }}
+ onSearch={() => { setCardList([]),generateChunkGroupCards(1, pageSize) }}
style={{
width: 500,
}}
@@ -330,7 +353,7 @@
<PageHeader
style={{ background: "#f2f2f2" }}
extra={(
- <Button type="primary" onClick={() => showChunk(details)}>{intl.formatMessage({ id: 'tsviewer.more.structureMap', })}</Button>
+ <Button type="primary" loading={structureMapLoading} onClick={() => showChunk(details)}>{intl.formatMessage({ id: 'tsviewer.more.structureMap', })}</Button>
)}>
</PageHeader>
<Details></Details>
@@ -356,14 +379,18 @@
onClose={onCloseChunk}
open={openChunk}
>
- <div style={{ height: "80vh", overflow: "auto" }}>
- <Tree
- showLine={{ showLeafIcon: false }}
- showIcon={true}
- onSelect={onSelect}
- loadData={onLoadData}
- treeData={treeData}
- />
+ <div id="tree-div" style={{ height: "80vh", background: "white", margin: '4px 0px 0px 0px' }}>
+ <TreeArborist
+ openByDefault={false}
+ disableDrag={false}
+ width={"100%"}
+ height={treeHeight}
+ // paddingBottom={200}
+ //height={400}
+ data={treeData}
+ >
+ {Node}
+ </TreeArborist>
</div>
<Drawer
@@ -385,10 +412,10 @@
open={openPage}
>
<Table columns={columns} dataSource={pageData} scroll={{ x: 150 * columnsLength, y: "80vh" }}
- rowKey={(record)=>{
+ rowKey={(record) => {
return record[0];
}}
- pagination={{ pageSize: 100, showQuickJumper: true, position: ["bottomCenter"] }}
+ pagination={{ defaultPageSize: 100, showQuickJumper: true, position: ["bottomCenter"] }}
bordered />
</Drawer>
</Drawer>
diff --git a/tsfile-viewer-web-frontend/src/pages/tsfile-tool/v2/overview/moreIndexOfTimeseriesIndex.jsx b/tsfile-viewer-web-frontend/src/pages/tsfile-tool/v2/overview/moreIndexOfTimeseriesIndex.jsx
index e76b266..4d10eea 100644
--- a/tsfile-viewer-web-frontend/src/pages/tsfile-tool/v2/overview/moreIndexOfTimeseriesIndex.jsx
+++ b/tsfile-viewer-web-frontend/src/pages/tsfile-tool/v2/overview/moreIndexOfTimeseriesIndex.jsx
@@ -45,6 +45,7 @@
const [randomFlag, setRandomFlag] = useState()
const [indexTreeHeight, setIndexTreeHeight] = useState()
const { fileName, filePath, cardList, setCardList } = props;
+ const [treeHeight, setTreeHeight] = useState()
const intl = useIntl();
var pageDataCache;
@@ -64,16 +65,21 @@
}
let res = await getChunkListUsingPOST({ offset: node.position, filePath: filePath, offsetType: 'TS_INDEX', beginDate: beginDate, endDate: endDate })
if (res.code == 0) {
- setChunkTreeData(Object.values(res.data).map((chunkInfo) => {
+ setRandomFlag(moment(new Date()).valueOf())
+ let tree = Object.values(res.data).map((node) => {
let tree = {};
- tree['title'] = chunkInfo.measurementId;
- tree['key'] = chunkInfo.offset;
- tree['icon'] = <GroupOutlined />
- tree['timeseriesIndexOffset'] = node.position;
+ tree['name'] = node.measurementId;
+ tree['id'] = node.measurementId + '-' + node.offset + randomFlag;
+ // tree['icon'] = <RightOutlined />
tree['isLeaf'] = false;
+ tree['position'] = node.offset;
+ tree['timeseriesIndexOffset'] = node.position;
return tree;
- }))
+ })
+ setChunkTreeData(tree)
setopenChunk(true);
+ var div = document.getElementById('tree-div');
+ setTreeHeight(div.offsetHeight)
} else {
notification.error({
message: res.message,
@@ -81,93 +87,83 @@
}
};
- const onChunkSelect = async (selectedKeys, info) => {
- if (info.node.isLeaf == true) {
- let param = info.node.pageInfo;
- param['timeseriesIndexOffset'] = info.node.timeseriesIndexOffset;
- param['chunkOffset'] = info.node.chunkOffset;
- param['filePath'] = filePath;
- param['beginDate'] = beginDate;
- param['endDate'] = endDate;
- let res = await getPageInfoThroughTimeseriesIndexOffsetUsingPOST(param)
- if (res.code == 0) {
- //pagedata信息
- let cols = [{
- title: 'No',
- fixed: 'left',
- width: '100px',
- // render: (text, record, index) => `${index + 1}`, //每一页都从1开始
- render: (text, record, index) => {
- return index + 1
- }
+ const onChunkSelect = async (info) => {
+ if (!info.isLeaf) {
+ return
+ }
+ let param = info.pageInfo;
+ param['timeseriesIndexOffset'] = info.timeseriesIndexOffset;
+ param['chunkOffset'] = info.chunkOffset;
+ param['filePath'] = filePath;
+ param['beginDate'] = beginDate;
+ param['endDate'] = endDate;
+ let res = await getPageInfoThroughTimeseriesIndexOffsetUsingPOST(param)
+ if (res.code == 0) {
+ //pagedata信息
+ let cols = [{
+ title: 'No',
+ fixed: 'left',
+ width: '100px',
+ // render: (text, record, index) => `${index + 1}`, //每一页都从1开始
+ render: (text, record, index) => {
+ return index + 1
+ }
- }]
- cols.push(...Object.values(res.data.title).map((titleName, key) => {
- if (titleName == 'timestamp') {
- return {
- title: (
+ }]
+ cols.push(...Object.values(res.data.title).map((titleName, key) => {
+ if (titleName == 'timestamp') {
+ return {
+ title: (
+ <>
+ {titleName}<span>{'\u00A0\u00A0\u00A0\u00A0'}</span>
+ <RetweetOutlined
+ onClick={() => {
+ pageDataCache = Object.values(pageDataCache).map((item) => {
+ if ((item[0] + "").indexOf("-") > -1) {
+ item[0] = moment(item[0], 'YYYY-MM-DD HH:mm:ss.SSS').valueOf()
+ } else {
+ item[0] = moment(Number(item[0])).format('YYYY-MM-DD HH:mm:ss.SSS')
+ }
+ return item
+ })
+ setPageData(pageDataCache)
+ }}
+ />
+ </>),
+ dataIndex: titleName,
+ key: titleName,
+ fixed: 'left',
+ width: '250px',
+ render: (text, record, index) => {
+ return (
<>
- {titleName}<span>{'\u00A0\u00A0\u00A0\u00A0'}</span>
- <RetweetOutlined
- onClick={() => {
- pageDataCache = Object.values(pageDataCache).map((item)=>{
- if((item[0]+"").indexOf("-") > -1){
- item[0] = moment(item[0],'YYYY-MM-DD HH:mm:ss.SSS').valueOf()
- } else {
- item[0] = moment(Number(item[0])).format('YYYY-MM-DD HH:mm:ss.SSS')
- }
- return item
- })
- setPageData(pageDataCache)
- }}
- />
- </>),
- dataIndex: titleName,
- key: titleName,
- fixed: 'left',
- width: '250px',
- render: (text, record, index) => {
- return (
- <>
- <span id={index}>
- {record[key]}
- </span>
- {/* <span>{'\u00A0\u00A0\u00A0\u00A0'}</span>
- <RetweetOutlined
- onClick={(e) => {
- if (document.getElementById(index).innerText.indexOf("-") > -1) {
- document.getElementById(index).innerText = record[key]
- } else {
- document.getElementById(index).innerText = moment(Number(record[key])).format('YYYY-MM-DD HH:mm:ss.SSS')
- }
- }} /> */}
- </>
- )
-
- // return moment(Number(record[key])).format('YYYY-MM-DD HH:mm:ss.SSS')
- }
- }
- } else {
- return {
- title: titleName,
- dataIndex: titleName,
- key: titleName,
- render: (text, record, index) => {
- return record[key]
- }
+ <span id={index}>
+ {record[key]}
+ </span>
+ </>
+ )
}
}
- }))
- setColumnsLength(cols.length)
- setColumns(cols)
- setPageData(res.data.values);
- pageDataCache = res.data.values;
- showPage()
- } else {
- notification.error({
- message: res.message,
- });
- }
+ } else {
+ return {
+ title: titleName,
+ dataIndex: titleName,
+ key: titleName,
+ render: (text, record, index) => {
+ return record[key]
+ }
+ }
+ }
+ }))
+ setColumnsLength(cols.length)
+ setColumns(cols)
+ setPageData(res.data.values);
+ pageDataCache = res.data.values;
+ showPage()
+ } else {
+ notification.error({
+ message: res.message,
+ });
}
};
@@ -183,7 +179,7 @@
setopenPage(false);
};
- const updateIndexTreeData = (list, id, children) => {
+ const updateTreeData = (list, id, children) => {
return list.map((node) => {
if (node.id === id) {
return {
@@ -195,56 +191,37 @@
if (node.children) {
return {
...node,
- children: updateIndexTreeData(node.children, id, children),
+ children: updateTreeData(node.children, id, children),
};
}
return node;
});
}
- const updateTreeData = (list, key, children) => {
- return list.map((node) => {
- if (node.key === key) {
- return {
- ...node,
- children,
- };
+ const onLoadTreeData = async (expanded, node) => {
+ if (expanded && node.children == undefined) {
+ let res = await getPageListUsingPOST({ offset: node.position, filePath: filePath, beginDate: beginDateCache, endDate: endDateCache })
+ if (res.code == 0) {
+ let newTree = Object.values(res.data).map((child) => {
+ let tree = {};
+ tree['name'] = child.pageNo;
+ tree['id'] = child.pageNo + '-' + child.offset + randomFlag;
+ tree['isLeaf'] = true;
+ tree['position'] = child.offset;
+ tree['chunkOffset'] = node.position;
+ tree['timeseriesIndexOffset'] = node.timeseriesIndexOffset;
+ tree['pageInfo'] = child;
+ return tree;
+ })
+ let data = chunkTreeData;
+ let pa = updateTreeData(data, node.id, newTree);
+ setChunkTreeData(pa)
+ } else {
+ notification.error({
+ message: res.message,
+ });
}
- //这个应该是用来加载子节点的子节点的,应该需要修改children对象
- if (node.children) {
- return {
- ...node,
- children: updateTreeData(node.children, key, children),
- };
- }
- return node;
- });
- }
-
-
- const onLoadChunkTreeData = async ({ key, children, timeseriesIndexOffset }) => {
- let res = await getPageListUsingPOST({ offset: key, filePath: filePath, beginDate: beginDateCache, endDate: endDateCache })
- if (res.code == 0) {
- let pages = Object.values(res.data).map((pageInfo) => {
- let tree = {};
- tree['title'] = pageInfo.pageNo;
- tree['key'] = pageInfo.offset;
- tree['icon'] = <CopyOutlined />
- tree['isLeaf'] = true;
- tree['chunkOffset'] = key;
- tree['pageInfo'] = pageInfo;
- tree['timeseriesIndexOffset'] = timeseriesIndexOffset
- return tree;
- })
- setChunkTreeData(origin =>
- updateTreeData(origin, key, pages),
- );
- } else {
- notification.error({
- message: res.message,
- });
}
-
}
const onLoadIndexTreeData = async (expanded, node) => {
@@ -260,13 +237,8 @@
tree['position'] = child.position;
return tree;
})
- // not work
- // setIndexTreeData(origin =>
- // updateTreeData(origin, node.id, newTree),
- // );
let data = indexTreeData;
- let pa = updateIndexTreeData(data, node.id, newTree);
- console.log(pa)
+ let pa = updateTreeData(data, node.id, newTree);
setIndexTreeData(pa)
} else {
notification.error({
@@ -304,7 +276,19 @@
/* This node instance can do many things. See the API reference. */
return (
<div style={{ ...style, overflow: "hidden", width: "155vh", textOverflow: "ellipsis", whiteSpace: "nowrap" }} ref={dragHandle} onClick={() => (showChunk(node.data))}>
- {node.data.isLeaf ? "" : node.isOpen ? <MinusSquareOutlined onClick={() => (node.toggle())} /> : <PlusSquareOutlined onClick={() => (node.toggle())} />} <span style={{ background: node.isSelected ? "#FFDFD4" : "white" }}>{node.data.isLeaf ? <RightOutlined /> : ""}{node.data.name}</span>
+ {node.data.isLeaf ? "" : node.isOpen ? <MinusSquareOutlined onClick={() => (node.toggle())} /> : <PlusSquareOutlined onClick={() => (node.toggle())} />} <span style={{ background: node.isSelected && node.data.isLeaf ? "#FFDFD4" : "white" }}>{node.data.isLeaf ? <RightOutlined /> : ""}{node.data.name}</span>
+ </div>
+ );
+ }
+
+ function Node1({ node, style, dragHandle, tree }) {
+ if (!node.isOpen && !node.data.isLeaf && node.data.children == undefined && node.isSelected) {
+ onLoadTreeData(!node.isOpen, node.data)
+ }
+ /* This node instance can do many things. See the API reference. */
+ return (
+ <div style={{ ...style, overflow: "hidden", width: "155vh", textOverflow: "ellipsis", whiteSpace: "nowrap" }} ref={dragHandle} onClick={() => (onChunkSelect(node.data))}>
+ {node.data.isLeaf ? "" : node.isOpen ? <MinusSquareOutlined onClick={() => (node.toggle())} /> : <PlusSquareOutlined onClick={() => (node.toggle())} />} <span style={{ background: node.isSelected && node.data.isLeaf ? "#FFDFD4" : "white" }}>{node.data.isLeaf ? <CopyOutlined /> : <GroupOutlined />}{node.data.name}</span>
</div>
);
}
@@ -440,16 +424,18 @@
onClose={onCloseChunk}
open={openChunk}
>
- {/* style={{ height: "80vh", overflow: "auto" }} */}
- <div >
- <Tree
- height={'78vh'}
- showLine={{ showLeafIcon: false }}
- showIcon={true}
- onSelect={onChunkSelect}
- loadData={onLoadChunkTreeData}
- treeData={chunkTreeData}
- />
+ <div id="tree-div" style={{ height: "80vh", background: "white", margin: '4px 0px 0px 0px' }}>
+ <TreeArborist
+ openByDefault={false}
+ disableDrag={false}
+ width={"100%"}
+ height={treeHeight}
+ // paddingBottom={200}
+ //height={400}
+ data={chunkTreeData}
+ >
+ {Node1}
+ </TreeArborist>
</div>
<Drawer
diff --git a/tsfile-viewer-web-frontend/src/pages/tsfile-tool/v2/overview/moreTimeseriesIndex.jsx b/tsfile-viewer-web-frontend/src/pages/tsfile-tool/v2/overview/moreTimeseriesIndex.jsx
index e145a7f..1c3c1ee 100644
--- a/tsfile-viewer-web-frontend/src/pages/tsfile-tool/v2/overview/moreTimeseriesIndex.jsx
+++ b/tsfile-viewer-web-frontend/src/pages/tsfile-tool/v2/overview/moreTimeseriesIndex.jsx
@@ -17,13 +17,14 @@
import React, { useState, useEffect } from "react";
import { Card, Layout, Button, Drawer, Tree, Input, Tooltip, DatePicker, Pagination, notification, Table, PageHeader } from 'antd';
import styles from '../style.less'
-import { GroupOutlined, CopyOutlined, RetweetOutlined, QuestionCircleOutlined } from '@ant-design/icons';
+import { GroupOutlined, CopyOutlined, RetweetOutlined, MinusSquareOutlined, PlusSquareOutlined, QuestionCircleOutlined } from '@ant-design/icons';
import {
getTimeseriesIndexListUsingPOST, getChunkListUsingPOST, getPageListUsingPOST
, getPageInfoThroughTimeseriesIndexOffsetUsingPOST, getTimeseriesIndexInfoUsingPOST
} from '@/services/swagger1/tsfileViewerController'
import moment from 'moment';
import { useIntl } from 'umi';
+import { Tree as TreeArborist } from "react-arborist";
const { Sider, Content } = Layout;
const { Search } = Input;
@@ -50,6 +51,9 @@
const [deviceNameLike, setDeviceNameLike] = useState()
const { fileName, filePath, cardList, setCardList } = props;
const intl = useIntl();
+ const [structureMapLoading, setStructureMapLoading] = useState();
+ const [treeHeight, setTreeHeight] = useState()
+ const [randomFlag, setRandomFlag] = useState()
var pageDataCache;
@@ -78,17 +82,24 @@
if (details == undefined) {
return
}
+ setStructureMapLoading(true)
let res = await getChunkListUsingPOST({ offset: details.offset, filePath: filePath, offsetType: 'TS_INDEX', beginDate: beginDateCache, endDate: endDateCache })
if (res.code == 0) {
- setTreeData(Object.values(res.data).map((chunkInfo) => {
+ setRandomFlag(moment(new Date()).valueOf())
+ let tree = Object.values(res.data).map((node) => {
let tree = {};
- tree['title'] = chunkInfo.measurementId;
- tree['key'] = chunkInfo.offset;
- tree['icon'] = <GroupOutlined />
+ tree['name'] = node.measurementId;
+ tree['id'] = node.measurementId + '-' + node.offset + randomFlag;
+ // tree['icon'] = <RightOutlined />
tree['isLeaf'] = false;
+ tree['position'] = node.offset;
return tree;
- }))
+ })
+ setTreeData(tree)
setopenChunk(true);
+ var div = document.getElementById('tree-div');
+ setTreeHeight(div.offsetHeight)
+ setStructureMapLoading(false)
} else {
notification.error({
message: res.message,
@@ -96,49 +107,60 @@
}
};
- const updateTreeData = (list, key, children) => {
+ const updateTreeData = (list, id, children) => {
return list.map((node) => {
- if (node.key === key) {
+ if (node.id === id) {
return {
...node,
children,
};
}
- // 这个应该是用来加载子节点的子节点的,应该需要修改children对象
- // if (node.children) {
- // return {
- // ...node,
- // children: updateTreeData(node.children, key, children),
- // };
- // }
+ //这个应该是用来加载子节点的子节点的,应该需要修改children对象
+ if (node.children) {
+ return {
+ ...node,
+ children: updateTreeData(node.children, id, children),
+ };
+ }
return node;
});
}
-
- const onLoadData = async ({ key, children }) => {
- let res = await getPageListUsingPOST({ offset: key, filePath: filePath, beginDate: beginDateCache, endDate: endDateCache })
-
- if (res.code == 0) {
- let pages = Object.values(res.data).map((pageInfo) => {
- let tree = {};
- tree['title'] = pageInfo.pageNo;
- tree['key'] = pageInfo.offset;
- tree['icon'] = <CopyOutlined />
- tree['isLeaf'] = true;
- tree['chunkOffset'] = key;
- tree['pageInfo'] = pageInfo;
- return tree;
- })
- setTreeData(origin =>
- updateTreeData(origin, key, pages),
- );
- } else {
- notification.error({
- message: res.message,
- });
+ const onLoadTreeData = async (expanded, node) => {
+ if (expanded && node.children == undefined) {
+ let res = await getPageListUsingPOST({ offset: node.position, filePath: filePath, beginDate: beginDateCache, endDate: endDateCache })
+ if (res.code == 0) {
+ let newTree = Object.values(res.data).map((child) => {
+ let tree = {};
+ tree['name'] = child.pageNo;
+ tree['id'] = child.pageNo + '-' + child.offset + randomFlag;
+ tree['isLeaf'] = true;
+ tree['position'] = child.offset;
+ tree['chunkOffset'] = node.position;
+ tree['pageInfo'] = child;
+ return tree;
+ })
+ let data = treeData;
+ let pa = updateTreeData(data, node.id, newTree);
+ setTreeData(pa)
+ } else {
+ notification.error({
+ message: res.message,
+ });
+ }
}
+ }
+ function Node({ node, style, dragHandle, tree }) {
+ if (!node.isOpen && !node.data.isLeaf && node.data.children == undefined && node.isSelected) {
+ onLoadTreeData(!node.isOpen, node.data)
+ }
+ /* This node instance can do many things. See the API reference. */
+ return (
+ <div style={{ ...style, overflow: "hidden", width: "155vh", textOverflow: "ellipsis", whiteSpace: "nowrap" }} ref={dragHandle} onClick={() => (onSelect(node.data))}>
+ {node.data.isLeaf ? "" : node.isOpen ? <MinusSquareOutlined onClick={() => (node.toggle())} /> : <PlusSquareOutlined onClick={() => (node.toggle())} />} <span style={{ background: node.isSelected && node.data.isLeaf ? "#FFDFD4" : "white" }}>{node.data.isLeaf ? <CopyOutlined /> : <GroupOutlined />}{node.data.name}</span>
+ </div>
+ );
}
const onCloseChunk = () => {
@@ -153,92 +175,82 @@
setopenPage(false);
};
- const onSelect = async (selectedKeys, info) => {
- if (info.node.isLeaf == true) {
- let param = info.node.pageInfo;
- param['timeseriesIndexOffset'] = details.offset;
- param['chunkOffset'] = info.node.chunkOffset;
- param['filePath'] = filePath;
- param['beginDate'] = beginDate;
- param['endDate'] = endDate;
- let res = await getPageInfoThroughTimeseriesIndexOffsetUsingPOST(param)
- if (res.code == 0) {
- //pagedata信息
- let cols = [{
- title: 'No',
- fixed: 'left',
- width: '100px',
- // render: (text, record, index) => `${index + 1}`, //每一页都从1开始
- render: (text, record, index) => {
- return index + 1
- }
- }]
- cols.push(...Object.values(res.data.title).map((titleName, key) => {
- if (titleName == 'timestamp') {
- return {
- title: (
+ const onSelect = async (info) => {
+ if (!info.isLeaf) {
+ return
+ }
+ let param = info.pageInfo;
+ param['timeseriesIndexOffset'] = details.offset;
+ param['chunkOffset'] = info.chunkOffset;
+ param['filePath'] = filePath;
+ param['beginDate'] = beginDate;
+ param['endDate'] = endDate;
+ let res = await getPageInfoThroughTimeseriesIndexOffsetUsingPOST(param)
+ if (res.code == 0) {
+ //pagedata信息
+ let cols = [{
+ title: 'No',
+ fixed: 'left',
+ width: '100px',
+ // render: (text, record, index) => `${index + 1}`, //每一页都从1开始
+ render: (text, record, index) => {
+ return index + 1
+ }
+ }]
+ cols.push(...Object.values(res.data.title).map((titleName, key) => {
+ if (titleName == 'timestamp') {
+ return {
+ title: (
+ <>
+ {titleName}<span>{'\u00A0\u00A0\u00A0\u00A0'}</span>
+ <RetweetOutlined
+ onClick={() => {
+ pageDataCache = Object.values(pageDataCache).map((item) => {
+ if ((item[0] + "").indexOf("-") > -1) {
+ item[0] = moment(item[0], 'YYYY-MM-DD HH:mm:ss.SSS').valueOf()
+ } else {
+ item[0] = moment(Number(item[0])).format('YYYY-MM-DD HH:mm:ss.SSS')
+ }
+ return item
+ })
+ setPageData(pageDataCache)
+ }}
+ />
+ </>),
+ dataIndex: titleName,
+ key: titleName,
+ fixed: 'left',
+ width: '250px',
+ render: (text, record, index) => {
+ return (
<>
- {titleName}<span>{'\u00A0\u00A0\u00A0\u00A0'}</span>
- <RetweetOutlined
- onClick={() => {
- pageDataCache = Object.values(pageDataCache).map((item)=>{
- if((item[0]+"").indexOf("-") > -1){
- item[0] = moment(item[0],'YYYY-MM-DD HH:mm:ss.SSS').valueOf()
- } else {
- item[0] = moment(Number(item[0])).format('YYYY-MM-DD HH:mm:ss.SSS')
- }
- return item
- })
- setPageData(pageDataCache)
- }}
- />
- </>),
- dataIndex: titleName,
- key: titleName,
- fixed: 'left',
- width: '250px',
- render: (text, record, index) => {
- return (
- <>
- <span id={index}>
- {record[key]}
- </span>
- {/* <span>{'\u00A0\u00A0\u00A0\u00A0'}</span>
- <RetweetOutlined
- onClick={(e) => {
- if (document.getElementById(index).innerText.indexOf("-") > -1) {
- document.getElementById(index).innerText = record[key]
- } else {
- document.getElementById(index).innerText = moment(Number(record[key])).format('YYYY-MM-DD HH:mm:ss.SSS')
- }
- }} /> */}
- </>
- )
-
- // return moment(Number(record[key])).format('YYYY-MM-DD HH:mm:ss.SSS')
- }
- }
- } else {
- return {
- title: titleName,
- dataIndex: titleName,
- key: titleName,
- render: (text, record, index) => {
- return record[key]
- }
+ <span id={index}>
+ {record[key]}
+ </span>
+ </>
+ )
}
}
- }))
- setColumnsLength(cols.length)
- setColumns(cols)
- setPageData(res.data.values);
- pageDataCache = res.data.values;
- showPage()
- } else {
- notification.error({
- message: res.message,
- });
- }
+ } else {
+ return {
+ title: titleName,
+ dataIndex: titleName,
+ key: titleName,
+ render: (text, record, index) => {
+ return record[key]
+ }
+ }
+ }
+ }))
+ setColumnsLength(cols.length)
+ setColumns(cols)
+ setPageData(res.data.values);
+ pageDataCache = res.data.values;
+ showPage()
+ } else {
+ notification.error({
+ message: res.message,
+ });
}
};
@@ -352,7 +364,7 @@
onChange={(e) => {
setDeviceNameLike(e.target.value)
}}
- onSearch={() => generateTimeSeriesCards(1, pageSize)}
+ onSearch={() => {setCardList([]),generateTimeSeriesCards(1, pageSize)}}
style={{
width: 200,
}}
@@ -401,14 +413,18 @@
onClose={onCloseChunk}
open={openChunk}
>
- <div style={{ height: "80vh", overflow: "auto" }}>
- <Tree
- showLine={{ showLeafIcon: false }}
- showIcon={true}
- onSelect={onSelect}
- loadData={onLoadData}
- treeData={treeData}
- />
+ <div id="tree-div" style={{ height: "80vh", background: "white", margin: '4px 0px 0px 0px' }}>
+ <TreeArborist
+ openByDefault={false}
+ disableDrag={false}
+ width={"100%"}
+ height={treeHeight}
+ // paddingBottom={200}
+ //height={400}
+ data={treeData}
+ >
+ {Node}
+ </TreeArborist>
</div>
<Drawer
diff --git a/tsfile-viewer-web-frontend/src/pages/tsfile-tool/v2/overview/overview.jsx b/tsfile-viewer-web-frontend/src/pages/tsfile-tool/v2/overview/overview.jsx
index b3e8aa3..7373981 100644
--- a/tsfile-viewer-web-frontend/src/pages/tsfile-tool/v2/overview/overview.jsx
+++ b/tsfile-viewer-web-frontend/src/pages/tsfile-tool/v2/overview/overview.jsx
@@ -26,45 +26,44 @@
const Tsfile = (props) => {
const { fileName, baseInfo } = props;
- const intl = useIntl();
const doMessageShow = (msg, offset) => {
props.showStructureContext()
props.doChange(msg, offset)
}
- const getMessage = (wrap)=>{
+ const getMessage = (wrap) => {
let message;
let messageShow;
- if(wrap == undefined){
+ if (wrap == undefined) {
message = "";
messageShow = "";
- }else{
- message = <div className={styles.hcenter}>{wrap.deviceName }<br/>{"[" + wrap.offset + "]"}</div>;
- messageShow = wrap.deviceName +"\n[" + wrap.offset + "]";
+ } else {
+ message = <div className={styles.hcenter}>{wrap.deviceName}<br />{"[" + wrap.offset + "]"}</div>;
+ messageShow = wrap.deviceName + "\n[" + wrap.offset + "]";
}
return (
- <Tooltip placement="bottomLeft" title={<span style={{"whiteSpace":"pre-line"}}>{messageShow}</span>}>
+ <Tooltip placement="bottomLeft" title={<span style={{ "whiteSpace": "pre-line" }}>{messageShow}</span>}>
<h3 className={styles.hcenter}>{message}</h3>
</Tooltip>
)
}
- const getMessageIndex = (wrap)=>{
+ const getMessageIndex = (wrap) => {
let message;
let messageShow;
- if(wrap == undefined){
+ if (wrap == undefined) {
message = "";
messageShow = "";
- }else{
- if(!wrap.aligned){
- message = <div className={styles.hcenter}>{wrap.deviceId }<br/>{"[" + wrap.measurementId + "]"}<br/>{"[" + wrap.offset + "]"}</div>;
- messageShow = wrap.deviceId +"\n[" + wrap.measurementId + "]\n[" + wrap.offset + "]";
- }else{
- message = <div className={styles.hcenter}>{wrap.deviceId }<br/>{"[" + wrap.offset + "]"}</div>;
- messageShow = wrap.deviceId +"\n[" + wrap.offset + "]";
+ } else {
+ if (!wrap.aligned) {
+ message = <div className={styles.hcenter}>{wrap.deviceId}<br />{"[" + wrap.measurementId + "]"}<br />{"[" + wrap.offset + "]"}</div>;
+ messageShow = wrap.deviceId + "\n[" + wrap.measurementId + "]\n[" + wrap.offset + "]";
+ } else {
+ message = <div className={styles.hcenter}>{wrap.deviceId}<br />{"[" + wrap.offset + "]"}</div>;
+ messageShow = wrap.deviceId + "\n[" + wrap.offset + "]";
}
}
return (
- <Tooltip placement="bottomLeft" title={<span style={{"whiteSpace":"pre-line"}}>{message}</span>}>
+ <Tooltip placement="bottomLeft" title={<span style={{ "whiteSpace": "pre-line" }}>{message}</span>}>
<h3 className={styles.hcenter}>{message}</h3>
</Tooltip>
)
@@ -132,7 +131,7 @@
<div className={styles.notoplinerow}>
<Row align="middle" justify="center" style={{ height: "45px" }}>
<Col span={22} style={{ height: "40px" }}>
- <div className={styles.shortStyle} onClick={() => doMessageShow("TsfileMetaDataSize")}><h3 className={styles.hcenter}>TsfileMetaDataSize{"[" + baseInfo.metadataSize + "]"}</h3></div>
+ <div className={styles.shortStyle} onClick={() => doMessageShow("TsfileMetaDataSize")}><h3 className={styles.hcenter}>TsfileMetaDataSize{"[" + baseInfo.metadataSize + " bytes]"}</h3></div>
</Col>
</Row>
</div>
@@ -148,6 +147,7 @@
}
const ImageMessage = (props) => {
+ const intl = useIntl();
const { value, offset, showStructureContext, filePath } = props;
const [version, setVersion] = useState();
const [tsfileMetaDataSize, setTsfileMetaDataSize] = useState();
@@ -177,10 +177,10 @@
const showImage = (key, offset) => {
if (key == "TSFILE") {
getVersion();
- let message = '说明:\n' +
- 'TSFILE 魔数 offset=0 size=6\n' +
- 'VERSION:' + version + ' 版本 offset=6 size=1\n' +
- '文件末尾 TSFILE 标记结束 offset= 文件长度-6 size=6';
+ let message = intl.formatMessage({ id: 'overview.explanation', }) + ':\n' +
+ 'TSFILE, ' + intl.formatMessage({ id: 'overview.magicNumber', }) + ' offset=0 size=6\n' +
+ 'VERSION:' + version + ', ' + intl.formatMessage({ id: 'overview.version', }) + ' offset=6 size=1\n' +
+ intl.formatMessage({ id: 'overview.endExplanation', }) + ' offset= file.length-6 size=6';
return (<pre style={{ height: "55vh", overflow: "auto", whiteSpace: "pre-wrap" }}>{message}</pre>);
}
@@ -302,7 +302,7 @@
const [indexTimeseriesIndexBrief, setIndexTimeseriesIndexBrief] = useState()
// 结构CGH/CH/PH点击所对应的内容
const [structureContext, setStructureContext] = useState()
-
+ const intl = useIntl();
const { fileName, filePath, baseInfo } = props;
const doChange = (structureName, offset) => {
@@ -372,43 +372,43 @@
const showStructureContext = (level1Flag, level2Flag) => {
if (level1Flag == 'ChunkGroup') {
if (level2Flag == 'CGH') {
- let info = "结构说明:\n"
+ let info = intl.formatMessage({ id: 'overview.structureDescription', }) + ":\n"
+ "\t CGH = ChunkGroupHeader \n"
+ "\t CGD = ChunkGroupData \n"
+ "\t CGD = n * Chunk \n"
+ "\t ChunkGroup = CGH +CGD \n"
- + "内容详情: \n"
+ + intl.formatMessage({ id: 'overview.details', }) + ": \n"
setStructureContext(<pre style={{ height: "55vh", overflow: "auto", whiteSpace: "pre-wrap" }}>{info}{JSON.stringify(chunkGroupBrief.cgh, null, '\t')}</pre>)
}
if (level2Flag == 'CH') {
- let info = "结构说明:\n"
+ let info = intl.formatMessage({ id: 'overview.structureDescription', }) + ":\n"
+ "\t CH = ChunkHeader \n"
+ "\t CD = ChunkData \n"
+ "\t CD = n * Page \n"
+ "\t Chunk = CH +CD \n"
- + "内容详情: \n"
+ + intl.formatMessage({ id: 'overview.details', }) + ": \n"
setStructureContext(<pre style={{ height: "55vh", overflow: "auto", whiteSpace: "pre-wrap" }}>{info}{JSON.stringify(chunkGroupBrief.ch, null, '\t')}</pre>)
}
if (level2Flag == 'PH') {
- let info = "结构说明:\n"
+ let info = intl.formatMessage({ id: 'overview.structureDescription', }) + ":\n"
+ "\t PH = PageHeader \n"
+ "\t PD = PageData \n"
+ "\t Page = PH + PD \n"
- + "内容详情: \n"
+ + intl.formatMessage({ id: 'overview.details', }) + ": \n"
setStructureContext(<pre style={{ height: "55vh", overflow: "auto", whiteSpace: "pre-wrap" }}>{info}{JSON.stringify(chunkGroupBrief.ph, null, '\t')}</pre>)
}
} else if (level1Flag == 'TimeseriesIndex') {
if (level2Flag == 'TM') {
- let info = "结构说明:\n"
+ let info = intl.formatMessage({ id: 'overview.structureDescription', }) + ":\n"
+ "\t TM = TimeseriesMetadata \n"
+ "\t TimeseriesIndex = TM + n*CM \n"
- + "内容详情: \n"
+ + intl.formatMessage({ id: 'overview.details', }) + ": \n"
setStructureContext(<pre style={{ height: "55vh", overflow: "auto", whiteSpace: "pre-wrap" }}>{info}{JSON.stringify(timeseriesIndexBrief.tm, null, '\t')}</pre>)
}
if (level2Flag == 'CM') {
- let info = "结构说明:\n"
+ let info = intl.formatMessage({ id: 'overview.structureDescription', }) + ":\n"
+ "\t CM = ChunkMetadata \n"
- + "内容详情: \n"
+ + intl.formatMessage({ id: 'overview.details', }) + ": \n"
setStructureContext(<pre style={{ height: "55vh", overflow: "auto", whiteSpace: "pre-wrap" }}>{info}{JSON.stringify(timeseriesIndexBrief.cm, null, '\t')}</pre>)
}
} else if (level1Flag == 'IndexOfTimeseriesIndex') {
diff --git a/tsfile-viewer-web-frontend/src/pages/tsfile-tool/v2/searchData/searchPageData.jsx b/tsfile-viewer-web-frontend/src/pages/tsfile-tool/v2/searchData/searchPageData.jsx
index b96fe60..0f382d4 100644
--- a/tsfile-viewer-web-frontend/src/pages/tsfile-tool/v2/searchData/searchPageData.jsx
+++ b/tsfile-viewer-web-frontend/src/pages/tsfile-tool/v2/searchData/searchPageData.jsx
@@ -38,26 +38,25 @@
const intl = useIntl();
- const doQuery = async () => {
-
+ const doQuery = async (measurementValue) => {
if (device == undefined || device == null || device == '') {
notification.info({ message: "device " + intl.formatMessage({ id: 'tsviewer.more.notNull', }) })
return;
}
- if (measurement == undefined || measurement == null || measurement == '') {
+ if (measurementValue == undefined || measurementValue == null || measurementValue == '') {
notification.info({ message: "measurement " + intl.formatMessage({ id: 'tsviewer.more.notNull', }) })
return;
}
if (beginDate == undefined || beginDate == null || beginDate == '') {
- notification.info({ message: "beginDate " } + intl.formatMessage({ id: 'tsviewer.more.notNull', }))
+ notification.info({ message: "beginDate " + intl.formatMessage({ id: 'tsviewer.more.notNull', }) })
return;
}
if (endDate == undefined || endDate == null || endDate == '') {
- notification.info({ message: "device " + intl.formatMessage({ id: 'tsviewer.more.notNull', }) })
+ notification.info({ message: "endDate " + intl.formatMessage({ id: 'tsviewer.more.notNull', }) })
return;
}
@@ -134,7 +133,7 @@
onChange={(e) => {
setMeasureMent(e.target.value)
}}
- onSearch={() => doQuery()}
+ onSearch={(value) => doQuery(value)}
style={{
width: 200,
}}
diff --git a/tsfile-viewer-web/Dockerfile b/tsfile-viewer-web/Dockerfile
new file mode 100644
index 0000000..c71d36e
--- /dev/null
+++ b/tsfile-viewer-web/Dockerfile
@@ -0,0 +1,26 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+FROM openjdk:8
+COPY target/iotdb-tsfile-viewer-web-0.13.2-SNAPSHOT.jar app.jar
+COPY docker.yml application.yml
+EXPOSE 8080
+RUN bash -c 'touch /app.jar'
+RUN bash -c 'touch /application.yml'
+VOLUME /tsfile
+ENTRYPOINT ["java","-jar","/app.jar","--spring.config.location=application.yml"]
\ No newline at end of file
diff --git a/tsfile-viewer-web/docker.yml b/tsfile-viewer-web/docker.yml
new file mode 100644
index 0000000..f6b194e
--- /dev/null
+++ b/tsfile-viewer-web/docker.yml
@@ -0,0 +1,49 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+server:
+ port: 8080
+ servlet:
+ session:
+ cookie:
+ http-only: false
+spring:
+ servlet:
+ multipart:
+ max-file-size: 300MB
+ max-request-size: 300MB
+ main:
+ allow-bean-definition-overriding: true
+ messages:
+ basename: messages/message
+ encoding: utf-8
+management:
+ endpoint:
+ shutdown:
+ enabled: true
+ endpoints:
+ web:
+ exposure:
+ include: '*'
+swagger:
+ enable: true
+tsviewer:
+ web:
+ baseDirectory: /tsfile
+ containerSize: 5
\ No newline at end of file
diff --git a/tsfile-viewer-web/src/main/java/org/apache/iotdb/ui/config/TsfileViewerContainer.java b/tsfile-viewer-web/src/main/java/org/apache/iotdb/ui/config/TsfileViewerContainer.java
index 8c1af53..78477f0 100644
--- a/tsfile-viewer-web/src/main/java/org/apache/iotdb/ui/config/TsfileViewerContainer.java
+++ b/tsfile-viewer-web/src/main/java/org/apache/iotdb/ui/config/TsfileViewerContainer.java
@@ -21,6 +21,7 @@
import org.apache.iotdb.tool.core.service.TsFileAnalyserV13;
import org.apache.iotdb.ui.exception.TsfileViewerException;
+import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -34,7 +35,10 @@
@Configuration
public class TsfileViewerContainer {
- private Map<String, TsFileAnalyserV13> container = new ConcurrentHashMap(5);
+ @Value("${tsviewer.web.containerSize}")
+ private int containerSize;
+
+ private Map<String, TsFileAnalyserV13> container = new ConcurrentHashMap(containerSize);
/**
* 向容器中添加 parser
@@ -46,7 +50,7 @@
public void addTsfileParser(String key, TsFileAnalyserV13 tsFileAnalyserV13)
throws TsfileViewerException {
synchronized (container) {
- if (container.size() == 5) {
+ if (container.size() == containerSize) {
throw new TsfileViewerException(TsfileViewerException.CONTAINER_SIZE_REACHED_MAXIMUM, "");
}
}
@@ -91,7 +95,7 @@
* @return
*/
public boolean hasReachedMaximum() {
- if (container.size() >= 5) {
+ if (container.size() >= containerSize) {
return true;
}
return false;
diff --git a/tsfile-viewer-web/src/main/resources/application.yml b/tsfile-viewer-web/src/main/resources/application.yml
index 9b3ca16..80fc78f 100644
--- a/tsfile-viewer-web/src/main/resources/application.yml
+++ b/tsfile-viewer-web/src/main/resources/application.yml
@@ -45,4 +45,5 @@
enable: true
tsviewer:
web:
- baseDirectory: C:\Users\Administrator\Desktop\
\ No newline at end of file
+ baseDirectory: C:\Users\lilong\Desktop\
+ containerSize: 5
\ No newline at end of file