You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iotdb.apache.org by qi...@apache.org on 2022/11/23 15:01:54 UTC
[iotdb-tsfile-viewer] branch main updated: tsfile-viewer init,a tsfile visual management tool (#1)
This is an automated email from the ASF dual-hosted git repository.
qiaojialin pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iotdb-tsfile-viewer.git
The following commit(s) were added to refs/heads/main by this push:
new 98ab340 tsfile-viewer init,a tsfile visual management tool (#1)
98ab340 is described below
commit 98ab340d93dc7acbdcef55974f4c5b9eb474a559
Author: long.li <34...@users.noreply.github.com>
AuthorDate: Wed Nov 23 23:01:48 2022 +0800
tsfile-viewer init,a tsfile visual management tool (#1)
---
.gitignore | 23 +
README.md | 96 ++
pom.xml | 127 ++
tsfile-viewer-core/pom.xml | 50 +
.../iotdb/tool/core/model/AlignedPageInfo.java | 129 ++
.../iotdb/tool/core/model/AnalysedResultModel.java | 47 +
.../iotdb/tool/core/model/ChunkGroupInfo.java | 53 +
.../iotdb/tool/core/model/ChunkGroupMetaInfo.java | 104 ++
.../tool/core/model/ChunkGroupMetadataModel.java | 55 +
.../iotdb/tool/core/model/ChunkListInfo.java | 58 +
.../apache/iotdb/tool/core/model/ChunkModel.java | 70 +
.../iotdb/tool/core/model/DsTypeEncodeModel.java | 69 +
.../core/model/EncodeCompressAnalysedModel.java | 133 ++
.../apache/iotdb/tool/core/model/IPageInfo.java | 61 +
.../org/apache/iotdb/tool/core/model/PageInfo.java | 138 ++
.../tool/core/model/TimeSeriesMetadataNode.java | 115 ++
.../tool/core/model/web/ChunkGroupBriefInfo.java | 57 +
.../iotdb/tool/core/model/web/ChunkOffsetInfo.java | 74 +
.../tool/core/model/web/PageDataTableInfo.java | 48 +
.../tool/core/model/web/PageHeaderForWeb.java | 60 +
.../iotdb/tool/core/model/web/PageOffsetInfo.java | 133 ++
.../core/model/web/TimeseriesIndexOffsetInfo.java | 122 ++
.../iotdb/tool/core/service/TsFileAnalyserV13.java | 1602 ++++++++++++++++++++
.../tool/core/service/TsFileValuePageReader.java | 118 ++
.../iotdb/tool/core/util/OffLineTsFileUtil.java | 33 +
.../util/TsFileEncodeCompressAnalysedUtil.java | 422 ++++++
.../tool/core/service/TsFileAnalyserV13Test.java | 181 +++
tsfile-viewer-web-frontend/config/config.dev.js | 30 +
tsfile-viewer-web-frontend/config/config.js | 145 ++
.../config/defaultSettings.js | 32 +
tsfile-viewer-web-frontend/config/oneapi.json | 7 +
tsfile-viewer-web-frontend/config/proxy.js | 53 +
tsfile-viewer-web-frontend/config/routes.js | 68 +
tsfile-viewer-web-frontend/config/swagger.json | 1 +
tsfile-viewer-web-frontend/jest.config.js | 26 +
tsfile-viewer-web-frontend/jsconfig.json | 10 +
tsfile-viewer-web-frontend/package.json | 137 ++
tsfile-viewer-web-frontend/pom.xml | 91 ++
tsfile-viewer-web-frontend/public/favicon.ico | Bin 0 -> 16958 bytes
.../public/icons/icon-128x128.png | Bin 0 -> 1187 bytes
.../public/icons/icon-192x192.png | Bin 0 -> 1187 bytes
.../public/icons/icon-512x512.png | Bin 0 -> 1187 bytes
tsfile-viewer-web-frontend/public/iotdb-logo.png | Bin 0 -> 1187 bytes
tsfile-viewer-web-frontend/src/access.js | 24 +
tsfile-viewer-web-frontend/src/app.jsx | 105 ++
.../src/components/Footer/index.jsx | 29 +
.../src/components/HeaderDropdown/index.jsx | 26 +
.../src/components/HeaderDropdown/index.less | 32 +
.../src/components/HeaderSearch/index.jsx | 99 ++
.../src/components/HeaderSearch/index.less | 41 +
.../src/components/NoticeIcon/NoticeIcon.jsx | 130 ++
.../src/components/NoticeIcon/NoticeList.jsx | 113 ++
.../src/components/NoticeIcon/NoticeList.less | 119 ++
.../src/components/NoticeIcon/index.jsx | 163 ++
.../src/components/NoticeIcon/index.less | 51 +
.../src/e2e/baseLayout.e2e.js | 77 +
tsfile-viewer-web-frontend/src/global.less | 73 +
tsfile-viewer-web-frontend/src/locales/en-US.js | 32 +
.../src/locales/en-US/menu.js | 23 +
.../src/locales/en-US/pwa.js | 22 +
.../src/locales/en-US/tsviewer.js | 62 +
tsfile-viewer-web-frontend/src/locales/zh-CN.js | 32 +
.../src/locales/zh-CN/menu.js | 23 +
.../src/locales/zh-CN/pwa.js | 22 +
.../src/locales/zh-CN/tsviewer.js | 61 +
tsfile-viewer-web-frontend/src/manifest.json | 22 +
tsfile-viewer-web-frontend/src/pages/404.jsx | 34 +
tsfile-viewer-web-frontend/src/pages/Admin.jsx | 68 +
tsfile-viewer-web-frontend/src/pages/Welcome.jsx | 79 +
tsfile-viewer-web-frontend/src/pages/Welcome.less | 24 +
.../src/pages/exception/403/index.jsx | 33 +
.../src/pages/exception/404/index.jsx | 33 +
.../src/pages/exception/500/index.jsx | 33 +
.../pages/tsfile-tool/v2/fileSelect/fileSelect.jsx | 364 +++++
.../src/pages/tsfile-tool/v2/index.jsx | 100 ++
.../tsfile-tool/v2/overview/moreChunkGroup.jsx | 400 +++++
.../v2/overview/moreIndexOfTimeseriesIndex.jsx | 485 ++++++
.../v2/overview/moreTimeseriesIndex.jsx | 444 ++++++
.../src/pages/tsfile-tool/v2/overview/overview.jsx | 458 ++++++
.../pages/tsfile-tool/v2/overview/popDetails.jsx | 107 ++
.../tsfile-tool/v2/searchData/searchPageData.jsx | 153 ++
.../src/pages/tsfile-tool/v2/style.less | 255 ++++
tsfile-viewer-web-frontend/src/service-worker.js | 82 +
.../src/services/swagger1/index.ts | 24 +
.../services/swagger1/tsfileViewerController.ts | 315 ++++
.../src/services/swagger1/typings.d.ts | 194 +++
tsfile-viewer-web/pom.xml | 344 +++++
.../main/java/org/apache/iotdb/ui/Application.java | 84 +
.../ui/config/ApplicationContextProvider.java | 64 +
.../apache/iotdb/ui/config/ApplicationEvent.java | 38 +
.../org/apache/iotdb/ui/config/LocaleConfig.java | 52 +
.../org/apache/iotdb/ui/config/SecurityConfig.java | 47 +
.../org/apache/iotdb/ui/config/SwaggerConfig.java | 63 +
.../iotdb/ui/config/TsfileViewerContainer.java | 112 ++
.../ui/controller/TsfileViewerController.java | 727 +++++++++
.../iotdb/ui/controller/pagination/PageModel.java | 50 +
.../ui/controller/request/QueryByTimeReq.java | 42 +
.../request/QueryByTimeseriesIndexReq.java | 33 +
.../iotdb/ui/controller/request/QueryListReq.java | 59 +
.../iotdb/ui/controller/request/SearchDataReq.java | 62 +
.../apache/iotdb/ui/exception/BaseException.java | 41 +
.../apache/iotdb/ui/exception/FeedbackError.java | 44 +
.../iotdb/ui/exception/TsfileViewerException.java | 50 +
.../java/org/apache/iotdb/ui/model/BaseVO.java | 79 +
.../main/java/org/apache/iotdb/ui/model/Page.java | 78 +
.../java/org/apache/iotdb/ui/model/PageParam.java | 63 +
.../java/org/apache/iotdb/ui/model/UserDto.java | 54 +
.../ui/model/tsviewer/ChunkMetadataBriefVO.java | 43 +
.../apache/iotdb/ui/model/tsviewer/FileRecord.java | 71 +
.../apache/iotdb/ui/model/tsviewer/FileType.java | 25 +
.../iotdb/ui/model/tsviewer/ITITreeNode.java | 128 ++
.../apache/iotdb/ui/model/tsviewer/LoadStatus.java | 27 +
.../ui/model/tsviewer/MetadataIndexNodeVo.java | 68 +
.../apache/iotdb/ui/model/tsviewer/OffsetType.java | 25 +
.../apache/iotdb/ui/model/tsviewer/PageDataVO.java | 46 +
.../ui/model/tsviewer/TimeseriesIndexBriefVO.java | 43 +
.../iotdb/ui/model/tsviewer/TimeseriesIndexVO.java | 63 +
.../model/tsviewer/TimeseriesMetadataBriefVO.java | 75 +
.../iotdb/ui/model/tsviewer/TsfileMetadataVO.java | 43 +
.../ui/model/tsviewer/TsfilePropertiesVO.java | 43 +
.../iotdb/ui/service/TsfileViewerService.java | 822 ++++++++++
.../java/org/apache/iotdb/ui/util/IpUtils.java | 85 ++
.../java/org/apache/iotdb/ui/util/MessageUtil.java | 51 +
.../org/apache/iotdb/ui/util/VerifyCodeUtils.java | 290 ++++
.../src/main/resources/application.yml | 48 +
tsfile-viewer-web/src/main/resources/logback.xml | 33 +
.../src/main/resources/messages/message.properties | 18 +
.../resources/messages/message_en_US.properties | 30 +
.../resources/messages/message_zh_CN.properties | 29 +
.../src/main/resources/templates/favicon-16x16.png | Bin 0 -> 664 bytes
.../src/main/resources/templates/favicon-32x32.png | Bin 0 -> 627 bytes
.../src/main/resources/templates/index.html | 79 +
.../main/resources/templates/oauth2-redirect.html | 83 +
.../main/resources/templates/swagger-ui-bundle.js | 109 ++
.../resources/templates/swagger-ui-bundle.js.map | 16 +
.../templates/swagger-ui-standalone-preset.js | 30 +
.../templates/swagger-ui-standalone-preset.js.map | 17 +
.../src/main/resources/templates/swagger-ui.css | 19 +
.../main/resources/templates/swagger-ui.css.map | 17 +
.../src/main/resources/templates/swagger-ui.js | 25 +
.../src/main/resources/templates/swagger-ui.js.map | 17 +
.../src/test/resources/application.yml | 32 +
.../src/test/resources/logback-test.xml | 33 +
143 files changed, 14613 insertions(+)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..23ccd5e
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,23 @@
+/target/
+/.classpath
+/.project
+/.settings/
+/bin/
+.idea/
+*.iml
+tsfile-viewer/target/
+tsfile-viewer-core/target/
+frontend/target/
+frontend/node_modules/
+frontend/src/.umi/
+frontend/yarn.lock
+frontend/src/.umi-production/
+frontend/yarn.lock
+frontend/dist/
+frontend/.*
+frontend/package-lock.json
+frontend/yarn-error.log
+tsfile-viewer-web/src/main/resources/static/
+tsfile-viewer-web/target/
+tsfile-viewer-web-frontend/target/
+tsfile-viewer-web-frontend/node/
diff --git a/README.md b/README.md
index 19f1a75..630031a 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,97 @@
+<!--
+
+ 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.
+
+-->
+
# iotdb-tsfile-viewer
+[](https://github.com/apache/iotdb/actions/workflows/main-unix.yml)
+[](https://github.com/apache/iotdb/actions/workflows/main-win.yml)
+# Outline
+- [Introduction](#Introduction)
+- [Quick Start](#quick-start)
+ - [Prerequisites](#Prerequisites)
+ - [Compile](#Compile)
+- [Maintainers](#Maintainers)
+- [Contributing](#Contributing)
+- [Contributors](#Contributors)
+# 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
+- tsfile-viewer-core: core jar package project
+- tsfile-viewer-web: web viewer backend,When you execute the mvn install command in the parent project, it will package the front-end project code together
+- tsfile-viewer-web-frontend: web viewer frontend
+
+1. overview: This tool can Clearly display information of each part of TsFile, details are as follows:
+ 1. The versionNumber.
+ 2. The data layer: contains details of each level and statistic information.
+
+ i. ChunkGroup
+
+ ii. Chunk
+
+ iii. Page
+
+ iv. Point
+ 3. The index layer: displayed in a tree like structure then you can easily view the overall structure of the secondary
+ index(entity and measurement granularity).
+
+2. Timeseries and measurement search: In addition to displaying data, we also provide the function of querying TimeSeries by keyword. There is a linkage
+ between the index layer and the data layer, it can quickly locate the desired TimeSeries with details.
+
+<!-- 3. The encoding and compression type of a timeseries analysis: tsfile-mt provide the analysis of the current timeseries encoding and compression. In addition, tsfile-mt also provide the analysis
+ of the combination of various encoding and compression types of the timeseries. -->
+
+# Quick Start
+## Prerequisites
+To use the tool, you need to have:
+1. Java >= 1.8
+2. Maven >= 3.6
+
+Of course, you will also notice that there is a front-end project in the project. When you execute the mvn install command, the project will download its corresponding environment, and you don't have to configure the environment separately for it.
+## Compile
+You can download the source code from:
+```
+git@github.com:apache/iotdb-tsfile-viewer.git
+https://github.com/apache/iotdb-tsfile-viewer.git
+```
+Under the root path of iotdb-tsfile-viewer:
+```
+mvn clean install
+```
+then you can start this tool in the tsfile-viewer-web project
+
+```
+java -jar iotdb-tsfile-viewer-web-0.13.2-SNAPSHOT.jar
+```
+
+The default url is
+```
+http://localhost:8080/
+```
+You can modify the port through the file
+```
+iotdb-tsfile-viewer\tsfile-viewer-web-frontend\config\config.js
+tsfile-viewer-web\src\main\resources\application.yml
+```
+# Maintainers
+
+# Contributing
+Feel free to dive in! Open an issue or submit PRs.
+# Contributors
+This project exists thanks to all the people who contribute.
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..3b912c5
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,127 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ 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.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <packaging>pom</packaging>
+ <groupId>org.apache.iotdb</groupId>
+ <artifactId>iotdb-tsfile-viewer</artifactId>
+ <version>0.13.2-SNAPSHOT</version>
+ <name>IoTDB tsfile viewer</name>
+ <modules>
+ <module>tsfile-viewer-core</module>
+ <module>tsfile-viewer-web-frontend</module>
+ <module>tsfile-viewer-web</module>
+ </modules>
+ <properties>
+ <maven.compiler.source>1.8</maven.compiler.source>
+ <maven.compiler.target>1.8</maven.compiler.target>
+ <spotless.version>2.4.2</spotless.version>
+ <maven.assembly.version>3.1.0</maven.assembly.version>
+ </properties>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>com.diffplug.spotless</groupId>
+ <artifactId>spotless-maven-plugin</artifactId>
+ <version>${spotless.version}</version>
+ <configuration>
+ <java>
+ <googleJavaFormat>
+ <version>1.7</version>
+ <style>GOOGLE</style>
+ </googleJavaFormat>
+ <importOrder>
+ <order>org.apache.iotdb,,javax,java,\#</order>
+ </importOrder>
+ <removeUnusedImports/>
+ </java>
+ <lineEndings>UNIX</lineEndings>
+ </configuration>
+ <executions>
+ <execution>
+ <id>spotless-check</id>
+ <phase>validate</phase>
+ <goals>
+ <goal>check</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.rat</groupId>
+ <artifactId>apache-rat-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>license-check</id>
+ <phase>validate</phase>
+ <goals>
+ <goal>check</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ <pluginManagement>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.rat</groupId>
+ <artifactId>apache-rat-plugin</artifactId>
+ <version>0.13</version>
+ <configuration>
+ <consoleOutput>true</consoleOutput>
+ <excludes>
+ <!-- Git related files -->
+ <exclude>**/.git/**</exclude>
+ <exclude>**/.mvn/**</exclude>
+ <exclude>**/.gitignore</exclude>
+ <exclude>**/.gitmodules</exclude>
+ <exclude>**/.git-blame-ignore-revs</exclude>
+ <!-- Maven related files -->
+ <exclude>**/target/**</exclude>
+ <!-- Eclipse related files -->
+ <exclude>**/.project</exclude>
+ <exclude>**/.settings/**</exclude>
+ <exclude>**/.classpath</exclude>
+ <!-- IntelliJ related files -->
+ <exclude>**/.idea/**</exclude>
+ <exclude>**/*.iml</exclude>
+ <!-- Runtime log -->
+ <exclude>**/*.log</exclude>
+ <!-- generated by Github -->
+ <exclude>**/.github/**</exclude>
+ <!-- figures -->
+ <exclude>**/.png</exclude>
+ <exclude>**/.jpg</exclude>
+ <exclude>**/.jpeg</exclude>
+ <!-- JSON can't contain comments and therefore no Apache header -->
+ <exclude>*.json</exclude>
+ <exclude>**/*.json</exclude>
+ <exclude>**/yarn.lock</exclude>
+ <exclude>**/node/**</exclude>
+ <exclude>**/resources/static/**</exclude>
+ </excludes>
+ </configuration>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+ </build>
+</project>
diff --git a/tsfile-viewer-core/pom.xml b/tsfile-viewer-core/pom.xml
new file mode 100644
index 0000000..2d5c699
--- /dev/null
+++ b/tsfile-viewer-core/pom.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ 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.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.iotdb</groupId>
+ <artifactId>iotdb-tsfile-viewer</artifactId>
+ <version>0.13.2-SNAPSHOT</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+ <artifactId>iotdb-tsfile-viewer-core</artifactId>
+ <name>tsfile-viewer-core</name>
+ <properties>
+ <maven.compiler.source>8</maven.compiler.source>
+ <maven.compiler.target>8</maven.compiler.target>
+ <junit.version>4.13.2</junit.version>
+ </properties>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.iotdb</groupId>
+ <artifactId>tsfile</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>${junit.version}</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/model/AlignedPageInfo.java b/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/model/AlignedPageInfo.java
new file mode 100644
index 0000000..94174fb
--- /dev/null
+++ b/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/model/AlignedPageInfo.java
@@ -0,0 +1,129 @@
+/*
+ * 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.
+ */
+
+package org.apache.iotdb.tool.core.model;
+
+import org.apache.iotdb.tsfile.file.metadata.enums.CompressionType;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSEncoding;
+import org.apache.iotdb.tsfile.file.metadata.statistics.Statistics;
+
+import java.io.Serializable;
+import java.util.List;
+
+public class AlignedPageInfo implements IPageInfo {
+ private IPageInfo timePageInfo;
+ private List<IPageInfo> valuePageInfoList;
+
+ @Override
+ public int getUncompressedSize() {
+ return this.timePageInfo.getUncompressedSize();
+ }
+
+ @Override
+ public int getCompressedSize() {
+ return this.timePageInfo.getCompressedSize();
+ }
+
+ @Override
+ public long getPosition() {
+ return this.timePageInfo.getPosition();
+ }
+
+ @Override
+ public TSDataType getDataType() {
+ return this.timePageInfo.getDataType();
+ }
+
+ @Override
+ public TSEncoding getEncodingType() {
+ return this.timePageInfo.getEncodingType();
+ }
+
+ @Override
+ public CompressionType getCompressionType() {
+ return this.timePageInfo.getCompressionType();
+ }
+
+ @Override
+ public byte getChunkType() {
+ return this.timePageInfo.getChunkType();
+ }
+
+ @Override
+ public Statistics<? extends Serializable> getStatistics() {
+ return this.timePageInfo.getStatistics();
+ }
+
+ @Override
+ public void setUncompressedSize(int uncompressedSize) {
+ this.timePageInfo.setUncompressedSize(uncompressedSize);
+ }
+
+ @Override
+ public void setCompressedSize(int compressedSize) {
+ this.timePageInfo.setCompressedSize(compressedSize);
+ }
+
+ @Override
+ public void setPosition(long position) {
+ this.timePageInfo.setPosition(position);
+ }
+
+ @Override
+ public void setDataType(TSDataType dataType) {
+ this.timePageInfo.setDataType(dataType);
+ }
+
+ @Override
+ public void setEncodingType(TSEncoding encodingType) {
+ this.timePageInfo.setEncodingType(encodingType);
+ }
+
+ @Override
+ public void setCompressionType(CompressionType compressionType) {
+ this.timePageInfo.setCompressionType(compressionType);
+ }
+
+ @Override
+ public void setChunkType(byte chunkType) {
+ this.timePageInfo.setChunkType(chunkType);
+ }
+
+ @Override
+ public void setStatistics(Statistics<? extends Serializable> statistics) {
+ this.timePageInfo.setStatistics(statistics);
+ }
+
+ public IPageInfo getTimePageInfo() {
+ return timePageInfo;
+ }
+
+ public void setTimePageInfo(IPageInfo timePageInfo) {
+ this.timePageInfo = timePageInfo;
+ }
+
+ public List<IPageInfo> getValuePageInfoList() {
+ return valuePageInfoList;
+ }
+
+ public void setValuePageInfoList(List<IPageInfo> valuePageInfoList) {
+ this.valuePageInfoList = valuePageInfoList;
+ }
+}
diff --git a/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/model/AnalysedResultModel.java b/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/model/AnalysedResultModel.java
new file mode 100644
index 0000000..2a645f9
--- /dev/null
+++ b/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/model/AnalysedResultModel.java
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ */
+
+package org.apache.iotdb.tool.core.model;
+
+import java.util.List;
+
+public class AnalysedResultModel {
+
+ // 当前编码分析
+ private EncodeCompressAnalysedModel currentAnalysed;
+
+ // 所有编码分析列表(直接按序展示)
+ private List<EncodeCompressAnalysedModel> analysedList;
+
+ public EncodeCompressAnalysedModel getCurrentAnalysed() {
+ return currentAnalysed;
+ }
+
+ public void setCurrentAnalysed(EncodeCompressAnalysedModel currentAnalysed) {
+ this.currentAnalysed = currentAnalysed;
+ }
+
+ public List<EncodeCompressAnalysedModel> getAnalysedList() {
+ return analysedList;
+ }
+
+ public void setAnalysedList(List<EncodeCompressAnalysedModel> analysedList) {
+ this.analysedList = analysedList;
+ }
+}
diff --git a/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/model/ChunkGroupInfo.java b/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/model/ChunkGroupInfo.java
new file mode 100644
index 0000000..9c23eaa
--- /dev/null
+++ b/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/model/ChunkGroupInfo.java
@@ -0,0 +1,53 @@
+/*
+ * 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.
+ */
+
+package org.apache.iotdb.tool.core.model;
+
+/**
+ * ChunkGroupInfo
+ *
+ * @author shenguanchu
+ */
+public class ChunkGroupInfo {
+ private String deviceName;
+ private long offset;
+
+ public ChunkGroupInfo() {}
+
+ public ChunkGroupInfo(String deviceName, long offset) {
+ this.deviceName = deviceName;
+ this.offset = offset;
+ }
+
+ public String getDeviceName() {
+ return deviceName;
+ }
+
+ public void setDeviceName(String deviceName) {
+ this.deviceName = deviceName;
+ }
+
+ public long getOffset() {
+ return offset;
+ }
+
+ public void setOffset(long offset) {
+ this.offset = offset;
+ }
+}
diff --git a/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/model/ChunkGroupMetaInfo.java b/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/model/ChunkGroupMetaInfo.java
new file mode 100644
index 0000000..13b3a4a
--- /dev/null
+++ b/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/model/ChunkGroupMetaInfo.java
@@ -0,0 +1,104 @@
+/*
+ * 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.
+ */
+
+package org.apache.iotdb.tool.core.model;
+
+import org.apache.iotdb.tsfile.file.header.ChunkHeader;
+import org.apache.iotdb.tsfile.file.metadata.IChunkMetadata;
+
+import java.util.List;
+
+/**
+ * 读取 ChunkGroup 下的 Chunk 时,需要更新的信息
+ *
+ * @author shenguanchu
+ */
+public class ChunkGroupMetaInfo {
+ private byte marker;
+ private List<IChunkMetadata> chunkMetadataList;
+ private List<ChunkHeader> chunkHeaderList;
+ private List<long[]> timeBatch;
+ private IChunkMetadata alignedTimeChunkMetadata;
+ private List<IChunkMetadata> alignedValueChunkMetadata;
+
+ public ChunkGroupMetaInfo() {}
+
+ public ChunkGroupMetaInfo(
+ byte marker,
+ List<IChunkMetadata> chunkMetadataList,
+ List<ChunkHeader> chunkHeaderList,
+ List<long[]> timeBatch,
+ IChunkMetadata alignedTimeChunkMetadata,
+ List<IChunkMetadata> alignedValueChunkMetadata) {
+ this.marker = marker;
+ this.chunkMetadataList = chunkMetadataList;
+ this.chunkHeaderList = chunkHeaderList;
+ this.timeBatch = timeBatch;
+ this.alignedTimeChunkMetadata = alignedTimeChunkMetadata;
+ this.alignedValueChunkMetadata = alignedValueChunkMetadata;
+ }
+
+ public byte getMarker() {
+ return marker;
+ }
+
+ public void setMarker(byte marker) {
+ this.marker = marker;
+ }
+
+ public List<IChunkMetadata> getChunkMetadataList() {
+ return chunkMetadataList;
+ }
+
+ public void setChunkMetadataList(List<IChunkMetadata> chunkMetadataList) {
+ this.chunkMetadataList = chunkMetadataList;
+ }
+
+ public List<ChunkHeader> getChunkHeaderList() {
+ return chunkHeaderList;
+ }
+
+ public void setChunkHeaderList(List<ChunkHeader> chunkHeaderList) {
+ this.chunkHeaderList = chunkHeaderList;
+ }
+
+ public List<long[]> getTimeBatch() {
+ return timeBatch;
+ }
+
+ public void setTimeBatch(List<long[]> timeBatch) {
+ this.timeBatch = timeBatch;
+ }
+
+ public IChunkMetadata getAlignedTimeChunkMetadata() {
+ return alignedTimeChunkMetadata;
+ }
+
+ public void setAlignedTimeChunkMetadata(IChunkMetadata alignedTimeChunkMetadata) {
+ this.alignedTimeChunkMetadata = alignedTimeChunkMetadata;
+ }
+
+ public List<IChunkMetadata> getAlignedValueChunkMetadata() {
+ return alignedValueChunkMetadata;
+ }
+
+ public void setAlignedValueChunkMetadata(List<IChunkMetadata> alignedValueChunkMetadata) {
+ this.alignedValueChunkMetadata = alignedValueChunkMetadata;
+ }
+}
diff --git a/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/model/ChunkGroupMetadataModel.java b/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/model/ChunkGroupMetadataModel.java
new file mode 100644
index 0000000..4ff22d9
--- /dev/null
+++ b/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/model/ChunkGroupMetadataModel.java
@@ -0,0 +1,55 @@
+/*
+ * 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.
+ */
+package org.apache.iotdb.tool.core.model;
+
+import org.apache.iotdb.tsfile.file.header.ChunkHeader;
+import org.apache.iotdb.tsfile.file.metadata.IChunkMetadata;
+
+import java.util.List;
+
+/** Only maintained when writing, not serialized to TsFile */
+public class ChunkGroupMetadataModel {
+
+ private String device;
+
+ private List<IChunkMetadata> chunkMetadataList;
+
+ private List<List<ChunkHeader>> chunkHeaderLists;
+
+ public ChunkGroupMetadataModel(
+ String device,
+ List<IChunkMetadata> chunkMetadataList,
+ List<List<ChunkHeader>> chunkHeaderList) {
+ this.device = device;
+ this.chunkMetadataList = chunkMetadataList;
+ this.chunkHeaderLists = chunkHeaderList;
+ }
+
+ public String getDevice() {
+ return device;
+ }
+
+ public List<IChunkMetadata> getChunkMetadataList() {
+ return chunkMetadataList;
+ }
+
+ public List<List<ChunkHeader>> getChunkHeaderLists() {
+ return chunkHeaderLists;
+ }
+}
diff --git a/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/model/ChunkListInfo.java b/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/model/ChunkListInfo.java
new file mode 100644
index 0000000..48988c4
--- /dev/null
+++ b/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/model/ChunkListInfo.java
@@ -0,0 +1,58 @@
+/*
+ * 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.
+ */
+
+package org.apache.iotdb.tool.core.model;
+
+import org.apache.iotdb.tsfile.file.header.ChunkHeader;
+import org.apache.iotdb.tsfile.file.metadata.IChunkMetadata;
+
+import java.util.List;
+
+/**
+ * ChunkListInfo: the chunk list of a ChunkGroup
+ *
+ * @author shenguanchu
+ */
+public class ChunkListInfo {
+ private List<IChunkMetadata> chunkMetadataList;
+ private List<ChunkHeader> chunkHeaderList;
+
+ public ChunkListInfo() {}
+
+ public ChunkListInfo(List<IChunkMetadata> chunkMetadataList, List<ChunkHeader> chunkHeaderList) {
+ this.chunkMetadataList = chunkMetadataList;
+ this.chunkHeaderList = chunkHeaderList;
+ }
+
+ public List<IChunkMetadata> getChunkMetadataList() {
+ return chunkMetadataList;
+ }
+
+ public void setChunkMetadataList(List<IChunkMetadata> chunkMetadataList) {
+ this.chunkMetadataList = chunkMetadataList;
+ }
+
+ public List<ChunkHeader> getChunkHeaderList() {
+ return chunkHeaderList;
+ }
+
+ public void setChunkHeaderList(List<ChunkHeader> chunkHeaderList) {
+ this.chunkHeaderList = chunkHeaderList;
+ }
+}
diff --git a/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/model/ChunkModel.java b/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/model/ChunkModel.java
new file mode 100644
index 0000000..17689a6
--- /dev/null
+++ b/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/model/ChunkModel.java
@@ -0,0 +1,70 @@
+/*
+ * 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.
+ */
+
+package org.apache.iotdb.tool.core.model;
+
+import org.apache.iotdb.tsfile.file.header.PageHeader;
+import org.apache.iotdb.tsfile.file.metadata.ChunkMetadata;
+import org.apache.iotdb.tsfile.read.common.BatchData;
+import org.apache.iotdb.tsfile.read.common.Chunk;
+
+import java.util.List;
+
+public class ChunkModel {
+
+ private ChunkMetadata chunkMetadata;
+
+ private List<PageHeader> pageHeaders;
+
+ private List<BatchData> batchDataList;
+
+ private Chunk chunk;
+
+ public ChunkMetadata getChunkMetadata() {
+ return chunkMetadata;
+ }
+
+ public void setChunkMetadata(ChunkMetadata chunkMetadata) {
+ this.chunkMetadata = chunkMetadata;
+ }
+
+ public List<PageHeader> getPageHeaders() {
+ return pageHeaders;
+ }
+
+ public void setPageHeaders(List<PageHeader> pageHeaders) {
+ this.pageHeaders = pageHeaders;
+ }
+
+ public List<BatchData> getBatchDataList() {
+ return batchDataList;
+ }
+
+ public void setBatchDataList(List<BatchData> batchDataList) {
+ this.batchDataList = batchDataList;
+ }
+
+ public Chunk getChunk() {
+ return chunk;
+ }
+
+ public void setChunk(Chunk chunk) {
+ this.chunk = chunk;
+ }
+}
diff --git a/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/model/DsTypeEncodeModel.java b/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/model/DsTypeEncodeModel.java
new file mode 100644
index 0000000..28eb002
--- /dev/null
+++ b/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/model/DsTypeEncodeModel.java
@@ -0,0 +1,69 @@
+/*
+ * 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.
+ */
+
+package org.apache.iotdb.tool.core.model;
+
+import org.apache.iotdb.tsfile.encoding.encoder.Encoder;
+import org.apache.iotdb.tsfile.utils.PublicBAOS;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class DsTypeEncodeModel {
+
+ private String typeName;
+
+ private List<String> encodeNameList = new ArrayList<>();
+
+ private List<Encoder> encoders = new ArrayList<>();
+
+ private List<PublicBAOS> publicBAOS = new ArrayList<>();
+
+ public String getTypeName() {
+ return typeName;
+ }
+
+ public void setTypeName(String typeName) {
+ this.typeName = typeName;
+ }
+
+ public List<String> getEncodeNameList() {
+ return encodeNameList;
+ }
+
+ public void setEncodeNameList(List<String> encodeNameList) {
+ this.encodeNameList = encodeNameList;
+ }
+
+ public List<Encoder> getEncoders() {
+ return encoders;
+ }
+
+ public void setEncoders(List<Encoder> encoders) {
+ this.encoders = encoders;
+ }
+
+ public List<PublicBAOS> getPublicBAOS() {
+ return publicBAOS;
+ }
+
+ public void setPublicBAOS(List<PublicBAOS> publicBAOS) {
+ this.publicBAOS = publicBAOS;
+ }
+}
diff --git a/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/model/EncodeCompressAnalysedModel.java b/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/model/EncodeCompressAnalysedModel.java
new file mode 100644
index 0000000..d38503b
--- /dev/null
+++ b/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/model/EncodeCompressAnalysedModel.java
@@ -0,0 +1,133 @@
+/*
+ * 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.
+ */
+
+package org.apache.iotdb.tool.core.model;
+
+public class EncodeCompressAnalysedModel {
+ private String typeName;
+
+ private String encodeName;
+
+ private String compressName;
+
+ private long originSize;
+
+ private long encodedSize;
+
+ private long uncompressSize;
+
+ private long compressedSize;
+
+ private long compressedCost;
+
+ private double score;
+
+ public String getTypeName() {
+ return typeName;
+ }
+
+ public void setTypeName(String typeName) {
+ this.typeName = typeName;
+ }
+
+ public String getEncodeName() {
+ return encodeName;
+ }
+
+ public void setEncodeName(String encodeName) {
+ this.encodeName = encodeName;
+ }
+
+ public String getCompressName() {
+ return compressName;
+ }
+
+ public void setCompressName(String compressName) {
+ this.compressName = compressName;
+ }
+
+ public long getOriginSize() {
+ return originSize;
+ }
+
+ public void setOriginSize(long originSize) {
+ this.originSize = originSize;
+ }
+
+ public long getEncodedSize() {
+ return encodedSize;
+ }
+
+ public void setEncodedSize(long encodedSize) {
+ this.encodedSize = encodedSize;
+ }
+
+ public long getUncompressSize() {
+ return uncompressSize;
+ }
+
+ public void setUncompressSize(long uncompressSize) {
+ this.uncompressSize = uncompressSize;
+ }
+
+ public long getCompressedSize() {
+ return compressedSize;
+ }
+
+ public void setCompressedSize(long compressedSize) {
+ this.compressedSize = compressedSize;
+ }
+
+ public long getCompressedCost() {
+ return compressedCost;
+ }
+
+ public void setCompressedCost(long compressedCost) {
+ this.compressedCost = compressedCost;
+ }
+
+ public double getScore() {
+ return score;
+ }
+
+ public void setScore(double score) {
+ this.score = score;
+ }
+
+ public String toString() {
+ return "typeName : "
+ + typeName
+ + " compressName : "
+ + compressName
+ + " encodeName : "
+ + encodeName
+ + " score : "
+ + score
+ + " compressed cost : "
+ + compressedCost
+ + " compressedSize : "
+ + compressedSize
+ + " uncompressedSize : "
+ + uncompressSize
+ + " encodedSize : "
+ + encodedSize
+ + " originSize : "
+ + originSize;
+ }
+}
diff --git a/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/model/IPageInfo.java b/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/model/IPageInfo.java
new file mode 100644
index 0000000..b78bfa7
--- /dev/null
+++ b/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/model/IPageInfo.java
@@ -0,0 +1,61 @@
+/*
+ * 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.
+ */
+
+package org.apache.iotdb.tool.core.model;
+
+import org.apache.iotdb.tsfile.file.metadata.enums.CompressionType;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSEncoding;
+import org.apache.iotdb.tsfile.file.metadata.statistics.Statistics;
+
+import java.io.Serializable;
+
+public interface IPageInfo {
+ int getUncompressedSize();
+
+ int getCompressedSize();
+
+ long getPosition();
+
+ TSDataType getDataType();
+
+ TSEncoding getEncodingType();
+
+ CompressionType getCompressionType();
+
+ byte getChunkType();
+
+ Statistics<? extends Serializable> getStatistics();
+
+ void setUncompressedSize(int uncompressedSize);
+
+ void setCompressedSize(int compressedSize);
+
+ void setPosition(long position);
+
+ void setDataType(TSDataType dataType);
+
+ void setEncodingType(TSEncoding encodingType);
+
+ void setCompressionType(CompressionType compressionType);
+
+ void setChunkType(byte chunkType);
+
+ void setStatistics(Statistics<? extends Serializable> statistics);
+}
diff --git a/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/model/PageInfo.java b/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/model/PageInfo.java
new file mode 100644
index 0000000..123dd7f
--- /dev/null
+++ b/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/model/PageInfo.java
@@ -0,0 +1,138 @@
+/*
+ * 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.
+ */
+
+package org.apache.iotdb.tool.core.model;
+
+import org.apache.iotdb.tsfile.file.metadata.enums.CompressionType;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSEncoding;
+import org.apache.iotdb.tsfile.file.metadata.statistics.Statistics;
+
+import java.io.Serializable;
+
+public class PageInfo implements IPageInfo {
+
+ private int uncompressedSize;
+
+ private int compressedSize;
+
+ private long position;
+
+ private TSDataType dataType;
+
+ private TSEncoding encodingType;
+
+ private CompressionType compressionType;
+
+ private byte chunkType;
+
+ private Statistics<? extends Serializable> statistics;
+
+ public PageInfo() {}
+
+ public PageInfo(long position) {
+ this.position = position;
+ }
+
+ public int getUncompressedSize() {
+ return uncompressedSize;
+ }
+
+ public int getCompressedSize() {
+ return compressedSize;
+ }
+
+ public long getPosition() {
+ return position;
+ }
+
+ public TSDataType getDataType() {
+ return dataType;
+ }
+
+ public TSEncoding getEncodingType() {
+ return encodingType;
+ }
+
+ public CompressionType getCompressionType() {
+ return compressionType;
+ }
+
+ public byte getChunkType() {
+ return chunkType;
+ }
+
+ public Statistics<? extends Serializable> getStatistics() {
+ return statistics;
+ }
+
+ public void setUncompressedSize(int uncompressedSize) {
+ this.uncompressedSize = uncompressedSize;
+ }
+
+ public void setCompressedSize(int compressedSize) {
+ this.compressedSize = compressedSize;
+ }
+
+ public void setPosition(long position) {
+ this.position = position;
+ }
+
+ public void setDataType(TSDataType dataType) {
+ this.dataType = dataType;
+ }
+
+ public void setEncodingType(TSEncoding encodingType) {
+ this.encodingType = encodingType;
+ }
+
+ public void setCompressionType(CompressionType compressionType) {
+ this.compressionType = compressionType;
+ }
+
+ public void setChunkType(byte chunkType) {
+ this.chunkType = chunkType;
+ }
+
+ public void setStatistics(Statistics<? extends Serializable> statistics) {
+ this.statistics = statistics;
+ }
+
+ @Override
+ public String toString() {
+ return "PageInfo{"
+ + "uncompressedSize="
+ + uncompressedSize
+ + ", compressedSize="
+ + compressedSize
+ + ", position="
+ + position
+ + ", dataType="
+ + dataType
+ + ", encodingType="
+ + encodingType
+ + ", compressionType="
+ + compressionType
+ + ", chunkType="
+ + chunkType
+ + ", statistics="
+ + statistics
+ + '}';
+ }
+}
diff --git a/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/model/TimeSeriesMetadataNode.java b/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/model/TimeSeriesMetadataNode.java
new file mode 100644
index 0000000..79d1769
--- /dev/null
+++ b/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/model/TimeSeriesMetadataNode.java
@@ -0,0 +1,115 @@
+/*
+ * 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.
+ */
+
+package org.apache.iotdb.tool.core.model;
+
+import org.apache.iotdb.tsfile.file.metadata.ITimeSeriesMetadata;
+import org.apache.iotdb.tsfile.file.metadata.TimeseriesMetadata;
+import org.apache.iotdb.tsfile.file.metadata.enums.MetadataIndexNodeType;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class TimeSeriesMetadataNode {
+
+ private List<TimeSeriesMetadataNode> children = new ArrayList<>();
+
+ private MetadataIndexNodeType nodeType;
+
+ private String deviceId;
+
+ private String measurementId;
+
+ private long position;
+
+ private ITimeSeriesMetadata timeseriesMetadata;
+
+ private boolean aligned;
+
+ public TimeSeriesMetadataNode() {}
+
+ public TimeSeriesMetadataNode(
+ List<TimeSeriesMetadataNode> children,
+ MetadataIndexNodeType nodeType,
+ String deviceId,
+ String measurementId,
+ TimeseriesMetadata timeseriesMetadata) {
+ this.children = children;
+ this.nodeType = nodeType;
+ this.deviceId = deviceId;
+ this.measurementId = measurementId;
+ this.timeseriesMetadata = timeseriesMetadata;
+ }
+
+ public List<TimeSeriesMetadataNode> getChildren() {
+ return children;
+ }
+
+ public void setChildren(List<TimeSeriesMetadataNode> children) {
+ this.children = children;
+ }
+
+ public String getDeviceId() {
+ return deviceId;
+ }
+
+ public void setDeviceId(String deviceId) {
+ this.deviceId = deviceId;
+ }
+
+ public String getMeasurementId() {
+ return measurementId;
+ }
+
+ public void setMeasurementId(String measurementId) {
+ this.measurementId = measurementId;
+ }
+
+ public long getPosition() {
+ return position;
+ }
+
+ public void setPosition(long position) {
+ this.position = position;
+ }
+
+ public ITimeSeriesMetadata getTimeseriesMetadata() {
+ return timeseriesMetadata;
+ }
+
+ public void setTimeseriesMetadata(ITimeSeriesMetadata timeseriesMetadata) {
+ this.timeseriesMetadata = timeseriesMetadata;
+ }
+
+ public MetadataIndexNodeType getNodeType() {
+ return nodeType;
+ }
+
+ public void setNodeType(MetadataIndexNodeType nodeType) {
+ this.nodeType = nodeType;
+ }
+
+ public boolean isAligned() {
+ return aligned;
+ }
+
+ public void setAligned(boolean aligned) {
+ this.aligned = aligned;
+ }
+}
diff --git a/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/model/web/ChunkGroupBriefInfo.java b/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/model/web/ChunkGroupBriefInfo.java
new file mode 100644
index 0000000..4d67f31
--- /dev/null
+++ b/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/model/web/ChunkGroupBriefInfo.java
@@ -0,0 +1,57 @@
+/*
+ * 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.
+ */
+
+package org.apache.iotdb.tool.core.model.web;
+
+import org.apache.iotdb.tsfile.file.header.ChunkGroupHeader;
+import org.apache.iotdb.tsfile.file.header.ChunkHeader;
+
+/** @Author: LL @Description: @Date: create in 2022/10/19 16:45 */
+public class ChunkGroupBriefInfo {
+
+ private ChunkGroupHeader cgh;
+
+ private ChunkHeader ch;
+
+ private PageHeaderForWeb ph;
+
+ public ChunkGroupHeader getCgh() {
+ return cgh;
+ }
+
+ public void setCgh(ChunkGroupHeader cgh) {
+ this.cgh = cgh;
+ }
+
+ public ChunkHeader getCh() {
+ return ch;
+ }
+
+ public void setCh(ChunkHeader ch) {
+ this.ch = ch;
+ }
+
+ public PageHeaderForWeb getPh() {
+ return ph;
+ }
+
+ public void setPh(PageHeaderForWeb ph) {
+ this.ph = ph;
+ }
+}
diff --git a/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/model/web/ChunkOffsetInfo.java b/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/model/web/ChunkOffsetInfo.java
new file mode 100644
index 0000000..7ab3885
--- /dev/null
+++ b/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/model/web/ChunkOffsetInfo.java
@@ -0,0 +1,74 @@
+/*
+ * 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.
+ */
+
+package org.apache.iotdb.tool.core.model.web;
+
+/** @Author: LL @Description: @Date: create in 2022/10/20 17:35 */
+public class ChunkOffsetInfo {
+
+ private boolean isAligned;
+
+ private Long offset;
+
+ private String measurementId;
+
+ private long startTime;
+
+ private long endTime;
+
+ public boolean isAligned() {
+ return isAligned;
+ }
+
+ public void setAligned(boolean aligned) {
+ isAligned = aligned;
+ }
+
+ public Long getOffset() {
+ return offset;
+ }
+
+ public void setOffset(Long offset) {
+ this.offset = offset;
+ }
+
+ public String getMeasurementId() {
+ return measurementId;
+ }
+
+ public void setMeasurementId(String measurementId) {
+ this.measurementId = measurementId;
+ }
+
+ public long getStartTime() {
+ return startTime;
+ }
+
+ public void setStartTime(long startTime) {
+ this.startTime = startTime;
+ }
+
+ public long getEndTime() {
+ return endTime;
+ }
+
+ public void setEndTime(long endTime) {
+ this.endTime = endTime;
+ }
+}
diff --git a/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/model/web/PageDataTableInfo.java b/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/model/web/PageDataTableInfo.java
new file mode 100644
index 0000000..240cd5c
--- /dev/null
+++ b/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/model/web/PageDataTableInfo.java
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+package org.apache.iotdb.tool.core.model.web;
+
+import org.apache.iotdb.tsfile.read.common.BatchData;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** @Author: LL @Description: @Date: create in 2022/10/24 16:07 */
+public class PageDataTableInfo {
+
+ private List<String> title = new ArrayList<>();
+
+ private BatchData data;
+
+ public List<String> getTitle() {
+ return title;
+ }
+
+ public void setTitle(List<String> title) {
+ this.title = title;
+ }
+
+ public BatchData getData() {
+ return data;
+ }
+
+ public void setData(BatchData data) {
+ this.data = data;
+ }
+}
diff --git a/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/model/web/PageHeaderForWeb.java b/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/model/web/PageHeaderForWeb.java
new file mode 100644
index 0000000..02ddc1f
--- /dev/null
+++ b/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/model/web/PageHeaderForWeb.java
@@ -0,0 +1,60 @@
+/*
+ * 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.
+ */
+
+package org.apache.iotdb.tool.core.model.web;
+
+import org.apache.iotdb.tsfile.file.header.PageHeader;
+
+/** @Author: LL @Description: @Date: create in 2022/10/19 17:56 */
+public class PageHeaderForWeb {
+
+ private int uncompressedSize;
+ private int compressedSize;
+ private String statistics;
+
+ public PageHeaderForWeb(PageHeader pageHeader) {
+ uncompressedSize = pageHeader.getUncompressedSize();
+ compressedSize = pageHeader.getCompressedSize();
+ statistics = pageHeader.getStatistics() == null ? "" : pageHeader.getStatistics().toString();
+ }
+
+ public int getUncompressedSize() {
+ return uncompressedSize;
+ }
+
+ public void setUncompressedSize(int uncompressedSize) {
+ this.uncompressedSize = uncompressedSize;
+ }
+
+ public int getCompressedSize() {
+ return compressedSize;
+ }
+
+ public void setCompressedSize(int compressedSize) {
+ this.compressedSize = compressedSize;
+ }
+
+ public String getStatistics() {
+ return statistics;
+ }
+
+ public void setStatistics(String statistics) {
+ this.statistics = statistics;
+ }
+}
diff --git a/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/model/web/PageOffsetInfo.java b/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/model/web/PageOffsetInfo.java
new file mode 100644
index 0000000..ea4fc95
--- /dev/null
+++ b/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/model/web/PageOffsetInfo.java
@@ -0,0 +1,133 @@
+/*
+ * 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.
+ */
+package org.apache.iotdb.tool.core.model.web;
+
+import org.apache.iotdb.tsfile.file.metadata.enums.CompressionType;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSEncoding;
+
+/** @Author: LL @Description: @Date: create in 2022/10/21 15:12 */
+public class PageOffsetInfo {
+
+ /** page名称,逻辑概念,core包不负责填充 */
+ private String pageNo;;
+
+ private long offset;
+
+ private boolean isAligned;
+
+ /** 对应的chunkHeader中 获取的信息 */
+ private TSDataType tsDataType;
+
+ /** 对应的chunkHeader中 获取的信息 */
+ private TSEncoding encodingType;
+
+ /** 对应的chunkHeader中 获取的信息 */
+ private CompressionType compressionType;
+
+ /** 通过chunk的marker 判断出来的信息 */
+ private boolean hasStatistics;
+
+ /** 获取 pagedata时,页面传入的信息 */
+ private long chunkGroupOffset;
+
+ private long startTime;
+
+ private long endTime;
+
+ public long getOffset() {
+ return offset;
+ }
+
+ public void setOffset(long offset) {
+ this.offset = offset;
+ }
+
+ public boolean isAligned() {
+ return isAligned;
+ }
+
+ public void setAligned(boolean aligned) {
+ isAligned = aligned;
+ }
+
+ public String getPageNo() {
+ return pageNo;
+ }
+
+ public void setPageNo(String pageNo) {
+ this.pageNo = pageNo;
+ }
+
+ public TSDataType getTsDataType() {
+ return tsDataType;
+ }
+
+ public void setTsDataType(TSDataType tsDataType) {
+ this.tsDataType = tsDataType;
+ }
+
+ public TSEncoding getEncodingType() {
+ return encodingType;
+ }
+
+ public void setEncodingType(TSEncoding encodingType) {
+ this.encodingType = encodingType;
+ }
+
+ public CompressionType getCompressionType() {
+ return compressionType;
+ }
+
+ public void setCompressionType(CompressionType compressionType) {
+ this.compressionType = compressionType;
+ }
+
+ public boolean isHasStatistics() {
+ return hasStatistics;
+ }
+
+ public void setHasStatistics(boolean hasStatistics) {
+ this.hasStatistics = hasStatistics;
+ }
+
+ public long getChunkGroupOffset() {
+ return chunkGroupOffset;
+ }
+
+ public void setChunkGroupOffset(long chunkGroupOffset) {
+ this.chunkGroupOffset = chunkGroupOffset;
+ }
+
+ public long getStartTime() {
+ return startTime;
+ }
+
+ public void setStartTime(long startTime) {
+ this.startTime = startTime;
+ }
+
+ public long getEndTime() {
+ return endTime;
+ }
+
+ public void setEndTime(long endTime) {
+ this.endTime = endTime;
+ }
+}
diff --git a/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/model/web/TimeseriesIndexOffsetInfo.java b/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/model/web/TimeseriesIndexOffsetInfo.java
new file mode 100644
index 0000000..f39a354
--- /dev/null
+++ b/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/model/web/TimeseriesIndexOffsetInfo.java
@@ -0,0 +1,122 @@
+/*
+ * 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.
+ */
+package org.apache.iotdb.tool.core.model.web;
+
+import org.apache.iotdb.tsfile.file.metadata.enums.CompressionType;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSEncoding;
+
+/** @Author: LL @Description: @Date: create in 2022/10/26 14:28 */
+public class TimeseriesIndexOffsetInfo {
+
+ /** page名称,逻辑概念,core包不负责填充 */
+ private String pageNo;
+
+ private long offset;
+
+ private boolean isAligned;
+
+ /** 对应的chunkHeader中 获取的信息 */
+ private TSDataType tsDataType;
+
+ /** 对应的chunkHeader中 获取的信息 */
+ private TSEncoding encodingType;
+
+ /** 对应的chunkHeader中 获取的信息 */
+ private CompressionType compressionType;
+
+ /** 通过chunk的marker 判断出来的信息 */
+ private boolean hasStatistics;
+
+ private long timeseriesIndexOffset;
+
+ private long chunkOffset;
+
+ public String getPageNo() {
+ return pageNo;
+ }
+
+ public void setPageNo(String pageNo) {
+ this.pageNo = pageNo;
+ }
+
+ public long getOffset() {
+ return offset;
+ }
+
+ public void setOffset(long offset) {
+ this.offset = offset;
+ }
+
+ public boolean isAligned() {
+ return isAligned;
+ }
+
+ public void setAligned(boolean aligned) {
+ isAligned = aligned;
+ }
+
+ public long getTimeseriesIndexOffset() {
+ return timeseriesIndexOffset;
+ }
+
+ public void setTimeseriesIndexOffset(long timeseriesIndexOffset) {
+ this.timeseriesIndexOffset = timeseriesIndexOffset;
+ }
+
+ public TSDataType getTsDataType() {
+ return tsDataType;
+ }
+
+ public void setTsDataType(TSDataType tsDataType) {
+ this.tsDataType = tsDataType;
+ }
+
+ public TSEncoding getEncodingType() {
+ return encodingType;
+ }
+
+ public void setEncodingType(TSEncoding encodingType) {
+ this.encodingType = encodingType;
+ }
+
+ public CompressionType getCompressionType() {
+ return compressionType;
+ }
+
+ public void setCompressionType(CompressionType compressionType) {
+ this.compressionType = compressionType;
+ }
+
+ public boolean isHasStatistics() {
+ return hasStatistics;
+ }
+
+ public void setHasStatistics(boolean hasStatistics) {
+ this.hasStatistics = hasStatistics;
+ }
+
+ public long getChunkOffset() {
+ return chunkOffset;
+ }
+
+ public void setChunkOffset(long chunkOffset) {
+ this.chunkOffset = chunkOffset;
+ }
+}
diff --git a/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/service/TsFileAnalyserV13.java b/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/service/TsFileAnalyserV13.java
new file mode 100644
index 0000000..0b8c946
--- /dev/null
+++ b/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/service/TsFileAnalyserV13.java
@@ -0,0 +1,1602 @@
+/*
+ * 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.
+ */
+
+package org.apache.iotdb.tool.core.service;
+
+import org.apache.iotdb.tool.core.model.*;
+import org.apache.iotdb.tool.core.model.web.*;
+import org.apache.iotdb.tsfile.common.conf.TSFileConfig;
+import org.apache.iotdb.tsfile.common.conf.TSFileDescriptor;
+import org.apache.iotdb.tsfile.common.constant.TsFileConstant;
+import org.apache.iotdb.tsfile.encoding.decoder.Decoder;
+import org.apache.iotdb.tsfile.file.MetaMarker;
+import org.apache.iotdb.tsfile.file.header.ChunkGroupHeader;
+import org.apache.iotdb.tsfile.file.header.ChunkHeader;
+import org.apache.iotdb.tsfile.file.header.PageHeader;
+import org.apache.iotdb.tsfile.file.metadata.*;
+import org.apache.iotdb.tsfile.file.metadata.enums.MetadataIndexNodeType;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSEncoding;
+import org.apache.iotdb.tsfile.file.metadata.statistics.Statistics;
+import org.apache.iotdb.tsfile.fileSystem.FSFactoryProducer;
+import org.apache.iotdb.tsfile.read.TsFileReader;
+import org.apache.iotdb.tsfile.read.TsFileSequenceReader;
+import org.apache.iotdb.tsfile.read.common.BatchData;
+import org.apache.iotdb.tsfile.read.common.Path;
+import org.apache.iotdb.tsfile.read.expression.IExpression;
+import org.apache.iotdb.tsfile.read.expression.QueryExpression;
+import org.apache.iotdb.tsfile.read.expression.impl.BinaryExpression;
+import org.apache.iotdb.tsfile.read.expression.impl.GlobalTimeExpression;
+import org.apache.iotdb.tsfile.read.expression.impl.SingleSeriesExpression;
+import org.apache.iotdb.tsfile.read.filter.TimeFilter;
+import org.apache.iotdb.tsfile.read.filter.ValueFilter;
+import org.apache.iotdb.tsfile.read.query.dataset.QueryDataSet;
+import org.apache.iotdb.tsfile.read.reader.page.AlignedPageReader;
+import org.apache.iotdb.tsfile.read.reader.page.PageReader;
+import org.apache.iotdb.tsfile.read.reader.page.TimePageReader;
+import org.apache.iotdb.tsfile.read.reader.page.ValuePageReader;
+import org.apache.iotdb.tsfile.utils.Pair;
+import org.apache.iotdb.tsfile.utils.TsPrimitiveType;
+import org.apache.iotdb.tsfile.write.schema.IMeasurementSchema;
+import org.apache.iotdb.tsfile.write.schema.MeasurementSchema;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.nio.BufferOverflowException;
+import java.nio.ByteBuffer;
+import java.util.*;
+import java.util.concurrent.CountDownLatch;
+import java.util.stream.Collectors;
+
+import static org.apache.iotdb.tool.core.util.TsFileEncodeCompressAnalysedUtil.*;
+
+public class TsFileAnalyserV13 {
+
+ private static final Logger logger = LoggerFactory.getLogger(TsFileAnalyserV13.class);
+ private final long fileSize;
+ private final boolean seq = true;
+ private final String version;
+ private final String filePath;
+ private final TsFileAnalysedToolReader reader;
+ private final List<ChunkGroupMetadataModel> chunkGroupMetadataModelList = new ArrayList<>();
+ private final Map<Long, Pair<Path, ITimeSeriesMetadata>> timeseriesMetadataMap = new TreeMap<>();
+ private final Map<Path, IMeasurementSchema> newSchema = new HashMap<>();
+
+ private TimeSeriesMetadataNode timeSeriesMetadataNode;
+ // TODO 为什么定义成 final
+ /** chunkGroupInfoList */
+ private final List<ChunkGroupInfo> chunkGroupInfoList = new ArrayList<>();
+
+ private List<IMeasurementSchema> measurementSchemaList = new ArrayList<>();
+
+ private long allCount;
+
+ private double rateOfProcess;
+
+ private double loadOfPercent = 0.05;
+
+ private double parseOfPercent = 0.9;
+
+ private double indexOfPercent = 0.05;
+
+ private CountDownLatch countDownLatch = new CountDownLatch(1);
+
+ private final byte CHUNK_HEADER_MASK = (byte) 0x3F;
+
+ public TsFileAnalyserV13(String filePath) throws IOException {
+ this.filePath = filePath;
+ reader = new TsFileAnalysedToolReader(filePath);
+ fileSize = FSFactoryProducer.getFSFactory().getFile(filePath).length();
+ version = reader.readVersionNumber() + "";
+ rateOfProcess = loadOfPercent;
+ // 异步加载
+ new Thread(
+ () -> {
+ try {
+ initTsFileAnalysed();
+ timeSeriesMetadataNode = initTimeSeriesMetadataNodeAnalysed();
+ rateOfProcess = 1.0;
+ logger.info("init completed!");
+ countDownLatch.countDown();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ })
+ .start();
+ }
+
+ private void initTsFileAnalysed() throws IOException {
+
+ ChunkMetadata currentChunk;
+ String measurementID;
+ TSDataType dataType;
+ long fileOffsetOfChunk;
+
+ // ChunkMetadata of current ChunkGroup
+ List<IChunkMetadata> chunkMetadataList = new ArrayList<>();
+ List<List<ChunkHeader>> chunkHeaderLists = new ArrayList<>();
+
+ long headerLength = TSFileConfig.MAGIC_STRING.getBytes().length + Byte.BYTES;
+
+ reader.position(headerLength);
+
+ byte marker;
+ List<long[]> timeBatch = new ArrayList<>();
+ String lastDeviceId = null;
+ long lastChunkGroupPosition = 0;
+
+ // List<IMeasurementSchema> measurementSchemaList = new ArrayList<>();
+ IChunkMetadata alignedTimeChunk = null;
+ List<IChunkMetadata> alignedValueChunk = new ArrayList<>();
+ List<ChunkHeader> chunkHeaderList = new ArrayList<>();
+ // 0 NonAligned, 1 TimeColumn, 2 ValueColumn
+ int alignedFlag = 0;
+ try {
+ while ((marker = reader.readMarker()) != MetaMarker.SEPARATOR) {
+ switch (marker) {
+ case MetaMarker.CHUNK_HEADER:
+ case MetaMarker.TIME_CHUNK_HEADER:
+ case MetaMarker.VALUE_CHUNK_HEADER:
+ case MetaMarker.ONLY_ONE_PAGE_CHUNK_HEADER:
+ case MetaMarker.ONLY_ONE_PAGE_TIME_CHUNK_HEADER:
+ case MetaMarker.ONLY_ONE_PAGE_VALUE_CHUNK_HEADER:
+ ChunkHeader chunkHeader = reader.readChunkHeader(marker);
+ chunkHeaderList.add(chunkHeader);
+ // 跳过此 chunk,已经读取了 chunkHeader,直接加 dataSize 即可
+ reader.position(reader.position() + chunkHeader.getDataSize());
+ // 更新进度条
+ setRateOfProcess();
+ break;
+
+ case MetaMarker.CHUNK_GROUP_HEADER:
+ // if there is something wrong with the ChunkGroup Header, we will drop this ChunkGroup
+ // because we can not guarantee the correctness of the deviceId.
+ logger.info("Starting read a new ChunkGroupHeader, lastDeviceId:{}", lastDeviceId);
+ // 更新进度条
+ setRateOfProcess();
+ // lastChunkGroupPosition = reader.position();
+ if (lastDeviceId != null) {
+ // schema of last chunk group
+ // for (IMeasurementSchema tsSchema : measurementSchemaList) {
+ // newSchema.putIfAbsent(
+ // new Path(lastDeviceId, tsSchema.getMeasurementId()), tsSchema);
+ // }
+ // measurementSchemaList = new ArrayList<>();
+
+ // 存储上一个 ChunkGroup 已读取的信息
+ // if (alignedTimeChunk != null && alignedValueChunk.size() > 0) {
+ // chunkMetadataList.add(
+ // new AlignedChunkMetadata(alignedTimeChunk, new
+ // ArrayList<>(alignedValueChunk)));
+ // chunkHeaderLists.add(new ArrayList<>(chunkHeaderList));
+ // alignedValueChunk.clear();
+ // chunkHeaderList.clear();
+ // }
+ // chunkGroupMetadataModelList.add(
+ // new ChunkGroupMetadataModel(
+ // lastDeviceId,
+ // new ArrayList<>(chunkMetadataList),
+ // new ArrayList<>(chunkHeaderLists)));
+ // chunkMetadataList.clear();
+ // chunkHeaderLists.clear();
+
+ chunkGroupInfoList.add(new ChunkGroupInfo(lastDeviceId, lastChunkGroupPosition));
+ }
+
+ // this is a chunk group
+ // lastChunkGroupPosition = reader.position() - 1;
+ lastChunkGroupPosition = reader.position();
+ // chunkMetadataList = new ArrayList<>();
+ ChunkGroupHeader chunkGroupHeader = reader.readChunkGroupHeader();
+ lastDeviceId = chunkGroupHeader.getDeviceID();
+ break;
+ // plan index: 标记 chunkgroup 的结束(可用于类似断点续传、checkPoint、snapshot等)
+ case MetaMarker.OPERATION_INDEX_RANGE:
+ logger.info("Starting read OperationIndexRange, lastDeviceId:{}", lastDeviceId);
+ setRateOfProcess();
+ if (lastDeviceId != null) {
+ // schema of last chunk group
+ // for (IMeasurementSchema tsSchema : measurementSchemaList) {
+ // newSchema.putIfAbsent(
+ // new Path(lastDeviceId, tsSchema.getMeasurementId()), tsSchema);
+ // }
+ // measurementSchemaList = new ArrayList<>();
+ // if (alignedTimeChunk != null && alignedValueChunk.size() > 0) {
+ // chunkMetadataList.add(
+ // new AlignedChunkMetadata(alignedTimeChunk, new
+ // ArrayList<>(alignedValueChunk)));
+ // chunkHeaderLists.add(new ArrayList<>(chunkHeaderList));
+ // alignedValueChunk.clear();
+ // chunkHeaderList.clear();
+ // }
+ // // last chunk group Metadata
+ // chunkGroupMetadataModelList.add(
+ // new ChunkGroupMetadataModel(
+ // lastDeviceId,
+ // new ArrayList<>(chunkMetadataList), // 不需要
+ // new ArrayList<>(chunkHeaderLists))); // 需要
+ // chunkMetadataList.clear();
+ // chunkHeaderLists.clear();
+
+ chunkGroupInfoList.add(new ChunkGroupInfo(lastDeviceId, lastChunkGroupPosition));
+
+ lastDeviceId = null;
+ lastChunkGroupPosition = -1;
+ }
+ reader.readPlanIndex();
+ break;
+ default:
+ // the disk file is corrupted, using this file may be dangerous
+ logger.error("Unexpected marker:{}", marker);
+ throw new IOException("Unexpected marker " + marker);
+ }
+ }
+
+ // now we read the tail of the data section, so we are sure that the last
+ // ChunkGroupFooter is complete.
+ if (lastDeviceId != null && lastChunkGroupPosition != -1) {
+ logger.info("Read the tail of the data section, the lastDeviceId:{}", lastDeviceId);
+ setRateOfProcess();
+
+ chunkGroupInfoList.add(new ChunkGroupInfo(lastDeviceId, lastChunkGroupPosition));
+
+ // schema of last chunk group
+ // for (IMeasurementSchema tsSchema : measurementSchemaList) {
+ // newSchema.putIfAbsent(new Path(lastDeviceId, tsSchema.getMeasurementId()),
+ // tsSchema);
+ // }
+ // if (alignedTimeChunk != null && alignedValueChunk.size() > 0) {
+ // chunkMetadataList.add(
+ // new AlignedChunkMetadata(alignedTimeChunk, new
+ // ArrayList<>(alignedValueChunk)));
+ // chunkHeaderLists.add(new ArrayList<>(chunkHeaderList));
+ // alignedValueChunk.clear();
+ // chunkHeaderList.clear();
+ // }
+ // last chunk group Metadata
+ // chunkGroupMetadataModelList.add(
+ // new ChunkGroupMetadataModel(
+ // lastDeviceId,
+ // new ArrayList<>(chunkMetadataList),
+ // new ArrayList<>(chunkHeaderLists)));
+ // chunkMetadataList.clear();
+ // chunkHeaderLists.clear();
+ }
+
+ } catch (Exception e) {
+ logger.warn(
+ "TsFile {} self-check cannot proceed at position {}, recovered, because : {}",
+ filePath,
+ reader.position(),
+ e.getMessage());
+ }
+ }
+
+ private void setChunkStatistics(
+ Statistics<? extends Serializable> chunkStatistics,
+ long currentTime,
+ TsPrimitiveType value,
+ TSDataType dataType)
+ throws IOException {
+ switch (dataType) {
+ case INT32:
+ chunkStatistics.update(currentTime, value.getInt());
+ break;
+ case INT64:
+ chunkStatistics.update(currentTime, value.getLong());
+ break;
+ case FLOAT:
+ chunkStatistics.update(currentTime, value.getFloat());
+ break;
+ case DOUBLE:
+ chunkStatistics.update(currentTime, value.getDouble());
+ break;
+ case BOOLEAN:
+ chunkStatistics.update(currentTime, value.getBoolean());
+ break;
+ case TEXT:
+ chunkStatistics.update(currentTime, value.getBinary());
+ break;
+ default:
+ logger.error("Unexpected type:{}", dataType);
+ throw new IOException("Unexpected type " + dataType);
+ }
+ }
+
+ private void setRateOfProcess() throws IOException {
+ rateOfProcess = loadOfPercent + reader.position() / (double) fileSize * parseOfPercent;
+ }
+
+ private TimeSeriesMetadataNode initTimeSeriesMetadataNodeAnalysed() throws IOException {
+ return reader.getAllTimeseriesMetadataWithOffset();
+ }
+
+ /**
+ * 通过 ChunkGroup 的 offset 获取 ChunkGroup 的 Chunk 列表
+ *
+ * @param offset ChunkGroup 的 offset
+ */
+ public ChunkListInfo fetchChunkListByChunkGroupOffset(long offset) throws IOException {
+ List<IChunkMetadata> chunkMetadataList = new ArrayList<>();
+ List<ChunkHeader> chunkHeaderList = new ArrayList<>();
+ reader.position(offset);
+
+ // 1. 读 chunkGroup header
+ ChunkGroupHeader chunkGroupHeader = reader.readChunkGroupHeader();
+
+ List<long[]> timeBatch = new ArrayList<>();
+ List<IChunkMetadata> alignedValueChunkMetadata = new ArrayList<>();
+ byte marker = -1;
+
+ ChunkGroupMetaInfo chunkGroupMetaInfo =
+ new ChunkGroupMetaInfo(
+ marker, chunkMetadataList, chunkHeaderList, timeBatch, null, alignedValueChunkMetadata);
+
+ // 2. 读取 chunk
+ while (((marker = reader.readMarker()) != MetaMarker.CHUNK_GROUP_HEADER)
+ && (marker != MetaMarker.OPERATION_INDEX_RANGE)
+ && (marker != MetaMarker.SEPARATOR)) {
+ chunkGroupMetaInfo.setMarker(marker);
+ fetchChunkInfo(chunkGroupMetaInfo);
+ }
+
+ // 3. 存储上 ChunkGroup 已读取的信息
+ // 3.1 如果是对齐 Chunk
+ if (chunkGroupMetaInfo.getAlignedTimeChunkMetadata() != null
+ && chunkGroupMetaInfo.getAlignedValueChunkMetadata().size() > 0) {
+ chunkGroupMetaInfo
+ .getChunkMetadataList()
+ .add(
+ new AlignedChunkMetadata(
+ chunkGroupMetaInfo.getAlignedTimeChunkMetadata(),
+ new ArrayList<>(chunkGroupMetaInfo.getAlignedValueChunkMetadata())));
+ }
+
+ ChunkListInfo chunkListInfo =
+ new ChunkListInfo(
+ chunkGroupMetaInfo.getChunkMetadataList(), chunkGroupMetaInfo.getChunkHeaderList());
+
+ return chunkListInfo;
+ }
+
+ // 将某一 chunk 的元信息添加至相应 chunkgroup 的 List 中
+ public void fetchChunkInfo(ChunkGroupMetaInfo chunkGroupMetaInfo) throws IOException {
+ long chunkOffset = reader.position() - 1;
+ // ChunkHeader 中的 marker:判断 chunk 的开始,对齐或非对齐
+ ChunkHeader chunkHeader = reader.readChunkHeader(chunkGroupMetaInfo.getMarker());
+
+ // 0 NonAligned, 1 TimeColumn, 2 ValueColumn
+ int alignedFlag = 0;
+
+ String measurementID = chunkHeader.getMeasurementID();
+ IMeasurementSchema measurementSchema =
+ new MeasurementSchema(
+ measurementID,
+ chunkHeader.getDataType(),
+ chunkHeader.getEncodingType(),
+ chunkHeader.getCompressionType());
+ measurementSchemaList.add(measurementSchema);
+
+ // TODO 不需要吧
+ // 判断对其 or 非对齐
+ TSDataType dataType = chunkHeader.getDataType();
+ if (chunkHeader.getDataType() == TSDataType.VECTOR) {
+ chunkGroupMetaInfo.getTimeBatch().clear();
+ }
+
+ // 跳过此 chunk 下所有的 page
+ Statistics<? extends Serializable> chunkStatistics = Statistics.getStatsByType(dataType);
+ int chunkDataSize = chunkHeader.getDataSize();
+ if (chunkDataSize > 0) {
+ // 此 chunk 中的 page 数量超过一页,需要通过每个 pageHeader 中的 statistic 构建并更新 chunk 的 statistic
+ if (((byte) (chunkHeader.getChunkType() & CHUNK_HEADER_MASK)) == MetaMarker.CHUNK_HEADER) {
+ // 1. 若是对齐时间序列
+ // 1.1 TimeChunk(Pages)
+ if ((chunkHeader.getChunkType() & TsFileConstant.TIME_COLUMN_MASK)
+ == TsFileConstant.TIME_COLUMN_MASK) {
+ alignedFlag = 1;
+ } else if ((chunkHeader.getChunkType() & TsFileConstant.VALUE_COLUMN_MASK)
+ == TsFileConstant.VALUE_COLUMN_MASK) {
+ // 1.2 ValueChunk(Pages)
+ alignedFlag = 2;
+ }
+
+ // 2. 读取 Chunk 中所有的 page
+ while (chunkDataSize > 0) {
+ // a new Page
+ PageHeader pageHeader = reader.readPageHeader(chunkHeader.getDataType(), true);
+ if (pageHeader.getUncompressedSize() != 0) {
+ // not empty page
+ chunkStatistics.mergeStatistics(pageHeader.getStatistics());
+ }
+ // 跳过 Page 中具体的数据
+ reader.skipPageData(pageHeader);
+ // 减去 pageHeader 的长度
+ chunkDataSize -= pageHeader.getSerializedPageSize();
+ chunkHeader.increasePageNums(1);
+ }
+ } else {
+ // only one page without statistic, we need to iterate each point to generate chunk
+ // statistic
+ PageHeader pageHeader = reader.readPageHeader(chunkHeader.getDataType(), false);
+ System.out.println(
+ "only one page without statistic, reader position1:" + reader.position());
+ Decoder valueDecoder =
+ Decoder.getDecoderByType(chunkHeader.getEncodingType(), chunkHeader.getDataType());
+ ByteBuffer pageData = reader.readPage(pageHeader, chunkHeader.getCompressionType());
+ System.out.println(
+ "only one page without statistic, reader position2:" + reader.position());
+ Decoder timeDecoder =
+ Decoder.getDecoderByType(
+ TSEncoding.valueOf(TSFileDescriptor.getInstance().getConfig().getTimeEncoder()),
+ TSDataType.INT64);
+
+ if ((chunkHeader.getChunkType() & TsFileConstant.TIME_COLUMN_MASK)
+ == TsFileConstant.TIME_COLUMN_MASK) {
+ // Time Chunk with only one page
+ TimePageReader timePageReader = new TimePageReader(pageHeader, pageData, timeDecoder);
+ long[] currentTimeBatch = timePageReader.getNextTimeBatch();
+ chunkGroupMetaInfo.getTimeBatch().add(currentTimeBatch);
+ // 遍历每个时间点,并更新 chunk 的 statistic
+ for (long currentTime : currentTimeBatch) {
+ chunkStatistics.update(currentTime);
+ }
+ alignedFlag = 1;
+ } else if ((chunkHeader.getChunkType() & TsFileConstant.VALUE_COLUMN_MASK)
+ == TsFileConstant.VALUE_COLUMN_MASK) {
+ // Value Chunk with only one page
+ ValuePageReader valuePageReader =
+ new ValuePageReader(pageHeader, pageData, chunkHeader.getDataType(), valueDecoder);
+ TsPrimitiveType[] valueBatch =
+ valuePageReader.nextValueBatch(chunkGroupMetaInfo.getTimeBatch().get(0));
+ // 遍历每个数据点,并更新 chunk 的 statistic
+ if (valueBatch != null && valueBatch.length != 0) {
+ for (int i = 0; i < valueBatch.length; i++) {
+ TsPrimitiveType value = valueBatch[i];
+ if (value == null) {
+ continue;
+ }
+ long timeStamp = chunkGroupMetaInfo.getTimeBatch().get(0)[i];
+ setChunkStatistics(chunkStatistics, timeStamp, value, dataType);
+ }
+ }
+ alignedFlag = 2;
+ } else {
+ // NonAligned Chunk with only one page
+ PageReader pageReader =
+ new PageReader(
+ pageHeader, pageData, chunkHeader.getDataType(), valueDecoder, timeDecoder, null);
+ BatchData batchData = pageReader.getAllSatisfiedPageData();
+ while (batchData.hasCurrent()) {
+ setChunkStatistics(
+ chunkStatistics,
+ batchData.currentTime(),
+ batchData.currentTsPrimitiveType(),
+ dataType);
+ batchData.next();
+ }
+ alignedFlag = 0;
+ }
+ chunkHeader.increasePageNums(1);
+ }
+ }
+
+ // 构建 chunkMetadata
+ ChunkMetadata chunkMetadata =
+ new ChunkMetadata(measurementID, dataType, chunkOffset, chunkStatistics);
+
+ if (alignedFlag == 1) {
+ chunkGroupMetaInfo.setAlignedTimeChunkMetadata(chunkMetadata);
+ chunkGroupMetaInfo.getChunkHeaderList().add(chunkHeader);
+ } else if (alignedFlag == 2) {
+ chunkGroupMetaInfo.getAlignedValueChunkMetadata().add(chunkMetadata);
+ chunkGroupMetaInfo.getChunkHeaderList().add(chunkHeader);
+ } else if (alignedFlag == 3) {
+
+ chunkGroupMetaInfo.getChunkHeaderList().add(chunkHeader);
+ } else {
+ chunkGroupMetaInfo.getChunkMetadataList().add(chunkMetadata);
+ chunkGroupMetaInfo.getChunkHeaderList().add(chunkHeader);
+ }
+ }
+
+ /**
+ * 通过chunkMetadata获取chunk实例
+ *
+ * @return ChunkModel
+ */
+ public ChunkModel fetchChunkByChunkMetadata(ChunkMetadata chunkMetadata)
+ throws IOException, InterruptedException {
+
+ countDownLatch.await();
+ long offsetOfChunkHeader = chunkMetadata.getOffsetOfChunkHeader();
+ reader.position(offsetOfChunkHeader);
+ byte marker = reader.readMarker();
+ ChunkHeader chunkHeader = reader.readChunkHeader(marker);
+
+ Statistics<? extends Serializable> chunkStatistics =
+ Statistics.getStatsByType(chunkMetadata.getDataType());
+ int dataSize = chunkHeader.getDataSize();
+
+ List<PageHeader> pageHeaders = new ArrayList<>();
+
+ List<BatchData> batchDataList = new ArrayList<>();
+
+ if (((byte) (chunkHeader.getChunkType() & CHUNK_HEADER_MASK)) == MetaMarker.CHUNK_HEADER) {
+ while (dataSize > 0) {
+ // a new Page
+ PageHeader pageHeader = reader.readPageHeader(chunkHeader.getDataType(), true);
+ if (pageHeader.getUncompressedSize() != 0) {
+ // not empty page
+ chunkStatistics.mergeStatistics(pageHeader.getStatistics());
+ }
+ Decoder valueDecoder =
+ Decoder.getDecoderByType(chunkHeader.getEncodingType(), chunkHeader.getDataType());
+ ByteBuffer pageData = reader.readPage(pageHeader, chunkHeader.getCompressionType());
+ Decoder timeDecoder =
+ Decoder.getDecoderByType(
+ TSEncoding.valueOf(TSFileDescriptor.getInstance().getConfig().getTimeEncoder()),
+ TSDataType.INT64);
+ PageReader pageReader =
+ new PageReader(
+ pageHeader, pageData, chunkHeader.getDataType(), valueDecoder, timeDecoder, null);
+ BatchData batchData = pageReader.getAllSatisfiedPageData();
+
+ dataSize -= pageHeader.getSerializedPageSize();
+ chunkHeader.increasePageNums(1);
+
+ pageHeaders.add(pageHeader);
+ batchDataList.add(batchData);
+ }
+ } else {
+ // only one page without statistic, we need to iterate each point to generate
+ // statistic
+ PageHeader pageHeader = reader.readPageHeader(chunkHeader.getDataType(), false);
+ Decoder valueDecoder =
+ Decoder.getDecoderByType(chunkHeader.getEncodingType(), chunkHeader.getDataType());
+ ByteBuffer pageData = reader.readPage(pageHeader, chunkHeader.getCompressionType());
+ Decoder timeDecoder =
+ Decoder.getDecoderByType(
+ TSEncoding.valueOf(TSFileDescriptor.getInstance().getConfig().getTimeEncoder()),
+ TSDataType.INT64);
+ PageReader reader =
+ new PageReader(
+ pageHeader, pageData, chunkHeader.getDataType(), valueDecoder, timeDecoder, null);
+ BatchData batchData = reader.getAllSatisfiedPageData();
+
+ chunkHeader.increasePageNums(1);
+
+ pageHeaders.add(pageHeader);
+ batchDataList.add(batchData);
+ }
+ ChunkModel model = new ChunkModel();
+ model.setChunkMetadata(chunkMetadata);
+ model.setPageHeaders(pageHeaders);
+ model.setBatchDataList(batchDataList);
+ model.setChunk(reader.readMemChunk(chunkMetadata));
+ return model;
+ }
+
+ /**
+ * @param iChunkMetadata
+ * @return
+ * @throws IOException
+ * @throws InterruptedException
+ */
+ public List<IPageInfo> fetchPageInfoListByIChunkMetadata(IChunkMetadata iChunkMetadata)
+ throws IOException, InterruptedException {
+ countDownLatch.await();
+ List<IPageInfo> pageInfoList = new ArrayList<>();
+ if (iChunkMetadata instanceof AlignedChunkMetadata) {
+ AlignedChunkMetadata alignedChunkMetadata = (AlignedChunkMetadata) iChunkMetadata;
+ IChunkMetadata timeChunkMetadata = alignedChunkMetadata.getTimeChunkMetadata();
+ List<IChunkMetadata> valueChunkMetadatas = alignedChunkMetadata.getValueChunkMetadataList();
+ List<IPageInfo> timePageInfoList = fetchPageInfoListByChunkMetadata(timeChunkMetadata);
+ List<List<IPageInfo>> valuePageInfosList = new ArrayList<>();
+ for (IChunkMetadata valueChunkMetadata : valueChunkMetadatas) {
+ List<IPageInfo> valuePageInfoList = fetchPageInfoListByChunkMetadata(valueChunkMetadata);
+ valuePageInfosList.add(valuePageInfoList);
+ }
+ // 合并timePageInfo和valuePageInfo
+ for (int i = 0; i < timePageInfoList.size(); i++) {
+ AlignedPageInfo alignedPageInfos = new AlignedPageInfo();
+ alignedPageInfos.setTimePageInfo(timePageInfoList.get(i));
+ List<IPageInfo> valuePageInfoList = new ArrayList<>();
+ for (List<IPageInfo> iPageInfos : valuePageInfosList) {
+ valuePageInfoList.add(iPageInfos.get(i));
+ }
+ alignedPageInfos.setValuePageInfoList(valuePageInfoList);
+ pageInfoList.add(alignedPageInfos);
+ }
+
+ } else {
+ pageInfoList = fetchPageInfoListByChunkMetadata(iChunkMetadata);
+ }
+ return pageInfoList;
+ }
+
+ /** 获取PageInfo列表 */
+ public List<IPageInfo> fetchPageInfoListByChunkMetadata(IChunkMetadata chunkMetadata)
+ throws IOException {
+
+ long offsetOfChunkHeader = chunkMetadata.getOffsetOfChunkHeader();
+ reader.position(offsetOfChunkHeader);
+ byte marker = reader.readMarker();
+ ChunkHeader chunkHeader = reader.readChunkHeader(marker);
+
+ Statistics<? extends Serializable> chunkStatistics =
+ Statistics.getStatsByType(chunkMetadata.getDataType());
+ int dataSize = chunkHeader.getDataSize();
+
+ List<IPageInfo> pageInfoList = new ArrayList<>();
+
+ if (((byte) (chunkHeader.getChunkType() & CHUNK_HEADER_MASK)) == MetaMarker.CHUNK_HEADER) {
+ logger.info(
+ "read more than one page or read a page of aligned chunk, the chunkType:{}",
+ chunkHeader.getChunkType());
+ while (dataSize > 0) {
+ PageInfo pageInfo = new PageInfo(reader.position());
+ // a new Page
+ PageHeader pageHeader = reader.readPageHeader(chunkHeader.getDataType(), true);
+ if (pageHeader.getUncompressedSize() != 0) {
+ // not empty page
+ chunkStatistics.mergeStatistics(pageHeader.getStatistics());
+ pageInfo.setStatistics(pageHeader.getStatistics());
+ }
+ Decoder valueDecoder =
+ Decoder.getDecoderByType(chunkHeader.getEncodingType(), chunkHeader.getDataType());
+ ByteBuffer pageData = reader.readPage(pageHeader, chunkHeader.getCompressionType());
+ Decoder timeDecoder =
+ Decoder.getDecoderByType(
+ TSEncoding.valueOf(TSFileDescriptor.getInstance().getConfig().getTimeEncoder()),
+ TSDataType.INT64);
+ PageReader pageReader =
+ new PageReader(
+ pageHeader, pageData, chunkHeader.getDataType(), valueDecoder, timeDecoder, null);
+
+ pageInfo.setUncompressedSize(pageHeader.getUncompressedSize());
+ pageInfo.setCompressedSize(pageHeader.getCompressedSize());
+ pageInfo.setDataType(chunkHeader.getDataType());
+ pageInfo.setEncodingType(chunkHeader.getEncodingType());
+ pageInfo.setCompressionType(chunkHeader.getCompressionType());
+ pageInfo.setChunkType(chunkHeader.getChunkType());
+ pageInfoList.add(pageInfo);
+
+ dataSize -= pageHeader.getSerializedPageSize();
+ }
+ } else {
+ logger.info("read a page of aligned chunk, the chunkType:{}", chunkHeader.getChunkType());
+ PageInfo pageInfo = new PageInfo(reader.position());
+ // only one page without statistic, we need to iterate each point to generate
+ // statistic
+ PageHeader pageHeader = reader.readPageHeader(chunkHeader.getDataType(), false);
+ Decoder valueDecoder =
+ Decoder.getDecoderByType(chunkHeader.getEncodingType(), chunkHeader.getDataType());
+ ByteBuffer pageData = reader.readPage(pageHeader, chunkHeader.getCompressionType());
+ Decoder timeDecoder =
+ Decoder.getDecoderByType(
+ TSEncoding.valueOf(TSFileDescriptor.getInstance().getConfig().getTimeEncoder()),
+ TSDataType.INT64);
+ PageReader pageReader =
+ new PageReader(
+ pageHeader, pageData, chunkHeader.getDataType(), valueDecoder, timeDecoder, null);
+
+ pageInfo.setUncompressedSize(pageHeader.getUncompressedSize());
+ pageInfo.setCompressedSize(pageHeader.getCompressedSize());
+ pageInfo.setDataType(chunkHeader.getDataType());
+ pageInfo.setEncodingType(chunkHeader.getEncodingType());
+ pageInfo.setCompressionType(chunkHeader.getCompressionType());
+ pageInfo.setChunkType(chunkHeader.getChunkType());
+ pageInfoList.add(pageInfo);
+ }
+
+ return pageInfoList;
+ }
+
+ /**
+ * 获取页数据点迭代器
+ *
+ * @param pageInfo
+ * @return
+ * @throws IOException
+ */
+ public BatchData fetchBatchDataByPageInfo(IPageInfo pageInfo) throws IOException {
+
+ BatchData batchData;
+ Decoder timeDecoder =
+ Decoder.getDecoderByType(
+ TSEncoding.valueOf(TSFileDescriptor.getInstance().getConfig().getTimeEncoder()),
+ TSDataType.INT64);
+ // 非对齐时间序列
+ // if (pageInfo instanceof PageInfo) {
+ if (pageInfo.getDataType() != TSDataType.VECTOR) {
+ PageHeader pageHeader = fetchPageHeader(pageInfo);
+ Decoder valueDecoder =
+ Decoder.getDecoderByType(pageInfo.getEncodingType(), pageInfo.getDataType());
+ ByteBuffer pageData = reader.readPage(pageHeader, pageInfo.getCompressionType());
+
+ PageReader pageReader =
+ new PageReader(
+ pageHeader, pageData, pageInfo.getDataType(), valueDecoder, timeDecoder, null);
+
+ batchData = pageReader.getAllSatisfiedPageData();
+ } else {
+ // 对齐时间序列
+ IPageInfo timePageInfo = ((AlignedPageInfo) pageInfo).getTimePageInfo();
+ PageHeader timePageHeader = fetchPageHeader(timePageInfo);
+ ByteBuffer timeByteBuffer =
+ reader.readPage(timePageHeader, timePageInfo.getCompressionType());
+ // done
+ List<PageHeader> valuePageHeaders = new ArrayList<>();
+ List<ByteBuffer> valueByteBuffers = new ArrayList<>();
+ List<TSDataType> valueTSDataTypes = new ArrayList<>();
+ List<Decoder> valueDecoders = new ArrayList<>();
+ List<IPageInfo> valuePageInfoList = ((AlignedPageInfo) pageInfo).getValuePageInfoList();
+ for (IPageInfo valuePageInfo : valuePageInfoList) {
+ PageHeader valuePageHeader = fetchPageHeader(valuePageInfo);
+ valuePageHeaders.add(valuePageHeader);
+ valueByteBuffers.add(reader.readPage(valuePageHeader, valuePageInfo.getCompressionType()));
+ valueTSDataTypes.add(valuePageInfo.getDataType());
+ valueDecoders.add(
+ Decoder.getDecoderByType(valuePageInfo.getEncodingType(), valuePageInfo.getDataType()));
+ }
+
+ AlignedPageReader alignedPageReader =
+ new AlignedPageReader(
+ timePageHeader,
+ timeByteBuffer,
+ timeDecoder,
+ valuePageHeaders,
+ valueByteBuffers,
+ valueTSDataTypes,
+ valueDecoders,
+ null);
+
+ batchData = alignedPageReader.getAllSatisfiedPageData();
+ }
+ // else {
+ // batchData = null;
+ // }
+ return batchData;
+ }
+
+ private PageHeader fetchPageHeader(IPageInfo pageInfo) throws IOException {
+ // [uncompressedSize:int][compressedSize:int][statistics?][batchData]
+ reader.position(pageInfo.getPosition());
+ PageHeader pageHeader;
+ if (pageInfo.getStatistics() != null) {
+ pageHeader = reader.readPageHeader(pageInfo.getDataType(), true);
+ } else {
+ pageHeader = reader.readPageHeader(pageInfo.getDataType(), false);
+ }
+ return pageHeader;
+ }
+
+ /**
+ * 根据条件查询tsfile,返回查询结果
+ *
+ * @param startTimestamp
+ * @param endTimestamp
+ * @param device
+ * @param measurement
+ * @param value
+ * @param offset
+ * @param limit
+ * @return QueryDataSet
+ * @throws IOException
+ */
+ public QueryDataSet queryResult(
+ long startTimestamp,
+ long endTimestamp,
+ String device,
+ String measurement,
+ String value,
+ int offset,
+ int limit)
+ throws IOException, InterruptedException {
+ countDownLatch.await();
+ if (Objects.equals(device, "") || Objects.equals(measurement, "")) {
+ logger.warn(
+ "device or measurement is empty, please check. device:[{}], measurement:[{}]",
+ device,
+ measurement);
+ return null;
+ }
+ TsFileReader tsFileReader = new TsFileReader(reader);
+ Path path = new Path(device, measurement);
+ List<Path> paths = new ArrayList<>();
+ paths.add(path);
+ IExpression timeFilter;
+ if (startTimestamp > 0 && endTimestamp > 0) {
+ timeFilter =
+ BinaryExpression.and(
+ new GlobalTimeExpression(TimeFilter.gt(startTimestamp)),
+ new GlobalTimeExpression(TimeFilter.ltEq(endTimestamp)));
+ } else if (endTimestamp > 0) {
+ timeFilter = new GlobalTimeExpression(TimeFilter.ltEq(endTimestamp));
+ } else {
+ timeFilter = new GlobalTimeExpression(TimeFilter.gt(startTimestamp));
+ }
+
+ QueryExpression queryExpression;
+ if (!(Objects.equals(value, ""))) {
+ IExpression valueFilter = new SingleSeriesExpression(path, ValueFilter.eq(value));
+ IExpression finalExpression = BinaryExpression.and(timeFilter, valueFilter);
+ queryExpression = QueryExpression.create(paths, finalExpression);
+ } else {
+ queryExpression = QueryExpression.create(paths, timeFilter);
+ }
+ QueryDataSet result = tsFileReader.query(queryExpression);
+ if (limit > 0) {
+ result.setRowLimit(limit);
+ }
+ if (offset > 0) {
+ result.setRowOffset(offset);
+ }
+ logger.info("QueryExpression is: {}", queryExpression);
+ return result;
+ }
+
+ public AnalysedResultModel fetchAnalysedResultWithDeviceAndMeasurement(
+ String deviceId, String measurement) throws IOException {
+ List<ChunkMetadata> chunkMetadataList =
+ fetchChunkListsByDeviceIdAndMeasurementId(deviceId, measurement);
+ AnalysedResultModel resultModel = new AnalysedResultModel();
+ int countSize = 0;
+ long startTime = System.currentTimeMillis();
+ List<EncodeCompressAnalysedModel> allModelList = new ArrayList<>();
+ Map<String, EncodeCompressAnalysedModel> map = new HashMap<>();
+ String currentKey = "";
+ for (IChunkMetadata metadata : chunkMetadataList) {
+ long startTime1 = System.currentTimeMillis();
+ List<IPageInfo> pageInfoList = fetchPageInfoListByChunkMetadata(metadata);
+ for (IPageInfo pageInfo : pageInfoList) {
+ if (Objects.equals(currentKey, "")) {
+ currentKey =
+ pageInfo.getEncodingType().name() + "-" + pageInfo.getCompressionType().name();
+ }
+ if ((pageInfo.getChunkType() & TsFileConstant.VALUE_COLUMN_MASK)
+ == TsFileConstant.VALUE_COLUMN_MASK) {
+ // tsPrimitive
+ TsPrimitiveType[] values = fetchValueBatch(pageInfo);
+ countSize += values.length;
+ List<EncodeCompressAnalysedModel> models =
+ generateEncodeAndCompressAnalysedWithTsPrimitives(values);
+ if (models != null) {
+ allModelList.addAll(models);
+ }
+ } else {
+ // BatchData
+ BatchData batchData = fetchBatchDataByPageInfo(pageInfo);
+ countSize += batchData.length();
+ List<EncodeCompressAnalysedModel> models =
+ generateEncodeAndCompressAnalysedWithBatchData(batchData);
+ if (models != null) {
+ allModelList.addAll(models);
+ }
+ }
+ }
+ logger.info(
+ "metaData cost : "
+ + metadata.getMeasurementUid()
+ + (System.currentTimeMillis() - startTime1)
+ + " countSize : "
+ + countSize);
+ }
+
+ for (EncodeCompressAnalysedModel model : allModelList) {
+ String key = model.getEncodeName() + "-" + model.getCompressName();
+ if (map.containsKey(key)) {
+ EncodeCompressAnalysedModel existModel = map.get(key);
+ existModel.setOriginSize(existModel.getOriginSize() + model.getOriginSize());
+ existModel.setEncodedSize(existModel.getEncodedSize() + model.getEncodedSize());
+ existModel.setCompressedSize(existModel.getCompressedSize() + model.getCompressedSize());
+ existModel.setUncompressSize(existModel.getUncompressSize() + model.getUncompressSize());
+ existModel.setCompressedCost(existModel.getCompressedCost() + model.getCompressedCost());
+ } else {
+ map.put(key, model);
+ }
+ }
+ logger.info(
+ "encode analyse cost : "
+ + (System.currentTimeMillis() - startTime)
+ + " countSize : "
+ + countSize);
+ List<EncodeCompressAnalysedModel> sortedModels = sortedAnalysedModel(map);
+
+ resultModel.setAnalysedList(sortedModels);
+ resultModel.setCurrentAnalysed(map.get(currentKey));
+ return resultModel;
+ }
+
+ private List<ChunkMetadata> fetchChunkListsByDeviceIdAndMeasurementId(
+ String deviceId, String measurement) throws IOException {
+ List<ChunkMetadata> chunkList = reader.getChunkMetadataList(new Path(deviceId, measurement));
+ return chunkList;
+ }
+
+ private TsPrimitiveType[] fetchValueBatch(IPageInfo pageInfo) throws IOException {
+
+ PageHeader pageHeader = fetchPageHeader(pageInfo);
+ ByteBuffer dataBuffer = reader.readPage(pageHeader, pageInfo.getCompressionType());
+ Decoder valueDecoder =
+ Decoder.getDecoderByType(pageInfo.getEncodingType(), pageInfo.getDataType());
+ TsFileValuePageReader pageReader =
+ new TsFileValuePageReader(pageHeader, dataBuffer, pageInfo.getDataType(), valueDecoder);
+ return pageReader.allValueBatch();
+ }
+
+ /**
+ * **********************************************************for web
+ * clinet*************************************************************************************
+ */
+
+ /**
+ * 获取单个Chunkgroup的简易信息,chunkgroupHeader,第一个chunkHeader,第一个pageHeader
+ *
+ * @param offset
+ * @return
+ * @throws IOException
+ */
+ public ChunkGroupBriefInfo fetchChunkGroupBrief(long offset)
+ throws IOException, InterruptedException {
+ countDownLatch.await();
+ reader.position(offset);
+ ChunkGroupHeader chunkGroupHeader = reader.readChunkGroupHeader();
+ byte marker = reader.readMarker();
+ ChunkHeader chunkHeader = reader.readChunkHeader(marker);
+ byte pmarker = reader.readMarker();
+ PageHeader pageHeader;
+ if (marker == MetaMarker.CHUNK_HEADER) {
+ pageHeader = reader.readPageHeader(chunkHeader.getDataType(), true);
+ } else {
+ pageHeader = reader.readPageHeader(chunkHeader.getDataType(), false);
+ }
+
+ ChunkGroupBriefInfo chunkGroupBriefInfo = new ChunkGroupBriefInfo();
+ chunkGroupBriefInfo.setCgh(chunkGroupHeader);
+ chunkGroupBriefInfo.setCh(chunkHeader);
+ chunkGroupBriefInfo.setPh(new PageHeaderForWeb(pageHeader));
+ return chunkGroupBriefInfo;
+ }
+
+ /**
+ * 通过chunkgroup的offset 获取其下边所有的chunk
+ *
+ * @param offset
+ * @return
+ * @throws InterruptedException
+ * @throws IOException
+ */
+ public List<ChunkOffsetInfo> fetchChunkOffsetListByChunkGroupOffset(long offset)
+ throws InterruptedException, IOException {
+ countDownLatch.await();
+ List<ChunkOffsetInfo> chunkOffsetInfoList = new ArrayList<>();
+ reader.position(offset);
+ ChunkGroupHeader chunkGroupHeader = reader.readChunkGroupHeader();
+ byte marker;
+ ChunkHeader chunkHeader;
+ while (((marker = reader.readMarker()) != MetaMarker.CHUNK_GROUP_HEADER)
+ && (marker != MetaMarker.OPERATION_INDEX_RANGE)
+ && (marker != MetaMarker.SEPARATOR)) {
+ ChunkOffsetInfo chunkOffsetInfo = new ChunkOffsetInfo();
+ long chunkOffset = reader.position() - 1;
+ chunkHeader = reader.readChunkHeader(marker);
+ // aligned 对齐的时间序列,第一个chunk是VECTOR
+ if (chunkHeader.getDataType() == TSDataType.VECTOR) {
+ chunkOffsetInfo.setAligned(true);
+ }
+
+ if (chunkOffsetInfoList.size() != 0) {
+ chunkOffsetInfo.setAligned(chunkOffsetInfoList.get(0).isAligned());
+ }
+
+ chunkOffsetInfo.setMeasurementId(chunkHeader.getMeasurementID());
+ chunkOffsetInfo.setOffset(chunkOffset);
+ chunkOffsetInfoList.add(chunkOffsetInfo);
+ reader.position(reader.position() + chunkHeader.getDataSize());
+ }
+ return chunkOffsetInfoList;
+ }
+
+ public List<PageOffsetInfo> fetchPageOffsetListByChunkOffset(long offset)
+ throws IOException, InterruptedException {
+ countDownLatch.await();
+ List<PageOffsetInfo> pageOffsetInfoList = new ArrayList<>();
+ reader.position(offset);
+ byte marker = reader.readMarker();
+ ChunkHeader chunkHeader = reader.readChunkHeader(marker);
+ int chunkDataSize = chunkHeader.getDataSize();
+
+ boolean isAligned = false;
+ boolean hasStatistics = false;
+ if (chunkHeader.getDataType() == TSDataType.VECTOR) {
+ isAligned = true;
+ }
+
+ if (marker == MetaMarker.CHUNK_HEADER
+ || marker == MetaMarker.TIME_CHUNK_HEADER
+ || marker == MetaMarker.VALUE_CHUNK_HEADER) {
+ if (((byte) (chunkHeader.getChunkType() & 0x3F)) == MetaMarker.CHUNK_HEADER) {
+ hasStatistics = true;
+ }
+ }
+
+ while (chunkDataSize > 0) {
+ PageOffsetInfo pageOffsetInfo = new PageOffsetInfo();
+ pageOffsetInfo.setOffset(reader.position() - 1);
+ pageOffsetInfo.setAligned(isAligned);
+ pageOffsetInfo.setTsDataType(chunkHeader.getDataType());
+ PageHeader pageHeader = reader.readPageHeader(chunkHeader.getDataType(), hasStatistics);
+ pageOffsetInfo.setCompressionType(chunkHeader.getCompressionType());
+ pageOffsetInfo.setEncodingType(chunkHeader.getEncodingType());
+ pageOffsetInfo.setHasStatistics(hasStatistics);
+
+ // 界定page的startTime和endTime
+ if (hasStatistics) {
+ pageOffsetInfo.setStartTime(pageHeader.getStartTime());
+ pageOffsetInfo.setEndTime(pageHeader.getEndTime());
+ } else {
+ // 查找一下chunk offset 对应 chunkgroup offset
+ for (int i = chunkGroupInfoList.size() - 1; i >= 0; i--) {
+ if (chunkGroupInfoList.get(i).getOffset() - offset < 0) {
+ pageOffsetInfo.setChunkGroupOffset(chunkGroupInfoList.get(i).getOffset());
+ break;
+ }
+ }
+ BatchData data = fetchBatchDataByPageOffset(pageOffsetInfo).getData();
+ long startTime = 0;
+ long endTime = 0;
+ while (data.hasCurrent()) {
+ if (startTime == 0) {
+ startTime = data.currentTime();
+ endTime = data.currentTime();
+ }
+
+ if (data.currentTime() < startTime) {
+ startTime = data.currentTime();
+ }
+
+ if (data.currentTime() > endTime) {
+ endTime = data.currentTime();
+ }
+ data.next();
+ }
+ pageOffsetInfo.setStartTime(startTime);
+ pageOffsetInfo.setEndTime(endTime);
+ }
+
+ reader.skipPageData(pageHeader);
+ chunkDataSize -= pageHeader.getSerializedPageSize();
+ pageOffsetInfoList.add(pageOffsetInfo);
+ }
+ return pageOffsetInfoList;
+ }
+
+ public PageDataTableInfo fetchBatchDataByPageOffset(PageOffsetInfo pageOffsetInfo)
+ throws IOException, InterruptedException {
+
+ PageDataTableInfo tableInfo = new PageDataTableInfo();
+ BatchData batchData;
+ Decoder timeDecoder =
+ Decoder.getDecoderByType(
+ TSEncoding.valueOf(TSFileDescriptor.getInstance().getConfig().getTimeEncoder()),
+ TSDataType.INT64);
+
+ reader.position(pageOffsetInfo.getOffset());
+
+ if (!pageOffsetInfo.isAligned()) {
+ byte marker = reader.readMarker();
+ PageHeader pageHeader =
+ reader.readPageHeader(pageOffsetInfo.getTsDataType(), pageOffsetInfo.isHasStatistics());
+ Decoder valueDecoder =
+ Decoder.getDecoderByType(
+ pageOffsetInfo.getEncodingType(), pageOffsetInfo.getTsDataType());
+ ByteBuffer pageData = reader.readPage(pageHeader, pageOffsetInfo.getCompressionType());
+
+ PageReader pageReader =
+ new PageReader(
+ pageHeader,
+ pageData,
+ pageOffsetInfo.getTsDataType(),
+ valueDecoder,
+ timeDecoder,
+ null);
+ batchData = pageReader.getAllSatisfiedPageData();
+ tableInfo.getTitle().add("timestamp");
+ tableInfo.getTitle().add("value");
+ } else {
+ long cgOffset = pageOffsetInfo.getChunkGroupOffset();
+ List<ChunkOffsetInfo> chunkOffsetInfoList = fetchChunkOffsetListByChunkGroupOffset(cgOffset);
+ // ChunkOffsetInfo timeChunk = chunkOffsetInfoList.get(0);
+ // List<PageOffsetInfo> timePageInfoList =
+ // fetchPageOffsetListByChunkOffset(timeChunk.getOffset());
+ List<PageOffsetInfo> valuePageInfoList = new ArrayList<>();
+
+ tableInfo
+ .getTitle()
+ .addAll(
+ chunkOffsetInfoList.stream()
+ .map(chunkOffsetInfo -> chunkOffsetInfo.getMeasurementId())
+ .collect(Collectors.toList()));
+ tableInfo.getTitle().set(0, "timestamp");
+ // 查找当前page在timeChunk中的位置
+ List<PageOffsetInfo> pageOffsetListInTimeChunk =
+ fetchPageOffsetListByChunkOffset(chunkOffsetInfoList.get(0).getOffset());
+ int positionInTimmeChunkPages = 0;
+ for (int i = 0; i < pageOffsetListInTimeChunk.size(); i++) {
+ if (pageOffsetInfo.getOffset() == pageOffsetListInTimeChunk.get(i).getOffset()) {
+ positionInTimmeChunkPages = i;
+ break;
+ }
+ }
+
+ for (int i = 1; i < chunkOffsetInfoList.size(); i++) {
+ long valueChunkOffset = chunkOffsetInfoList.get(i).getOffset();
+ List<PageOffsetInfo> pageOffsetInfoList =
+ fetchPageOffsetListByChunkOffset(valueChunkOffset);
+ valuePageInfoList.add(pageOffsetInfoList.get(positionInTimmeChunkPages));
+ }
+
+ reader.position(pageOffsetInfo.getOffset());
+ reader.readMarker();
+ PageHeader timePageHeader =
+ reader.readPageHeader(pageOffsetInfo.getTsDataType(), pageOffsetInfo.isHasStatistics());
+ ByteBuffer timeByteBuffer =
+ reader.readPage(timePageHeader, pageOffsetInfo.getCompressionType());
+
+ List<PageHeader> valuePageHeaders = new ArrayList<>();
+ List<ByteBuffer> valueByteBuffers = new ArrayList<>();
+ List<TSDataType> valueTSDataTypes = new ArrayList<>();
+ List<Decoder> valueDecoders = new ArrayList<>();
+
+ for (PageOffsetInfo valuePageInfo : valuePageInfoList) {
+ reader.position(valuePageInfo.getOffset());
+ reader.readMarker();
+ PageHeader valuePageHeader =
+ reader.readPageHeader(valuePageInfo.getTsDataType(), valuePageInfo.isHasStatistics());
+
+ valuePageHeaders.add(valuePageHeader);
+ valueByteBuffers.add(reader.readPage(valuePageHeader, valuePageInfo.getCompressionType()));
+ valueTSDataTypes.add(valuePageInfo.getTsDataType());
+ valueDecoders.add(
+ Decoder.getDecoderByType(
+ valuePageInfo.getEncodingType(), valuePageInfo.getTsDataType()));
+ }
+ AlignedPageReader alignedPageReader =
+ new AlignedPageReader(
+ timePageHeader,
+ timeByteBuffer,
+ timeDecoder,
+ valuePageHeaders,
+ valueByteBuffers,
+ valueTSDataTypes,
+ valueDecoders,
+ null);
+
+ batchData = alignedPageReader.getAllSatisfiedPageData();
+ }
+ tableInfo.setData(batchData);
+ return tableInfo;
+ }
+
+ public PageDataTableInfo fetchBatchDataByTimeseriesIndexOffset(
+ TimeseriesIndexOffsetInfo timeseriesIndexOffsetInfo)
+ throws IOException, InterruptedException {
+
+ PageDataTableInfo tableInfo = new PageDataTableInfo();
+ BatchData batchData;
+ Decoder timeDecoder =
+ Decoder.getDecoderByType(
+ TSEncoding.valueOf(TSFileDescriptor.getInstance().getConfig().getTimeEncoder()),
+ TSDataType.INT64);
+
+ reader.position(timeseriesIndexOffsetInfo.getOffset());
+
+ if (!timeseriesIndexOffsetInfo.isAligned()) {
+ byte marker = reader.readMarker();
+ PageHeader pageHeader =
+ reader.readPageHeader(
+ timeseriesIndexOffsetInfo.getTsDataType(),
+ timeseriesIndexOffsetInfo.isHasStatistics());
+ Decoder valueDecoder =
+ Decoder.getDecoderByType(
+ timeseriesIndexOffsetInfo.getEncodingType(),
+ timeseriesIndexOffsetInfo.getTsDataType());
+ ByteBuffer pageData =
+ reader.readPage(pageHeader, timeseriesIndexOffsetInfo.getCompressionType());
+
+ PageReader pageReader =
+ new PageReader(
+ pageHeader,
+ pageData,
+ timeseriesIndexOffsetInfo.getTsDataType(),
+ valueDecoder,
+ timeDecoder,
+ null);
+ batchData = pageReader.getAllSatisfiedPageData();
+ tableInfo.getTitle().add("timestamp");
+ tableInfo.getTitle().add("value");
+ } else {
+ List<PageOffsetInfo> valuePageInfoList = new ArrayList<>();
+ long timeseriesIndexOffset = timeseriesIndexOffsetInfo.getTimeseriesIndexOffset();
+ Pair<Path, ITimeSeriesMetadata> pair = timeseriesMetadataMap.get(timeseriesIndexOffset);
+ AlignedTimeSeriesMetadata alignedMetadata = (AlignedTimeSeriesMetadata) pair.right;
+ AlignedChunkMetadata alignedChunkMetadata =
+ alignedMetadata.getChunkMetadataList().stream()
+ .filter(
+ alignedchunk -> {
+ if (alignedchunk.getOffsetOfChunkHeader()
+ == timeseriesIndexOffsetInfo.getChunkOffset()) {
+ return true;
+ }
+ return false;
+ })
+ .collect(Collectors.toList())
+ .get(0);
+
+ IChunkMetadata timeChunk = alignedChunkMetadata.getTimeChunkMetadata();
+ List<IChunkMetadata> valueChunkList = alignedChunkMetadata.getValueChunkMetadataList();
+
+ tableInfo.getTitle().add("timestamp");
+ tableInfo
+ .getTitle()
+ .addAll(
+ valueChunkList.stream()
+ .map(chunkOffsetInfo -> chunkOffsetInfo.getMeasurementUid())
+ .collect(Collectors.toList()));
+
+ // 查找当前page在timeChunk中的位置
+ List<PageOffsetInfo> pageOffsetListInTimeChunk =
+ fetchPageOffsetListByChunkOffset(timeChunk.getOffsetOfChunkHeader());
+ int positionInTimmeChunkPages = 0;
+ for (int i = 0; i < pageOffsetListInTimeChunk.size(); i++) {
+ if (timeseriesIndexOffsetInfo.getOffset() == pageOffsetListInTimeChunk.get(i).getOffset()) {
+ positionInTimmeChunkPages = i;
+ break;
+ }
+ }
+
+ for (int i = 0; i < valueChunkList.size(); i++) {
+ long valueChunkOffset = valueChunkList.get(i).getOffsetOfChunkHeader();
+ List<PageOffsetInfo> pageOffsetInfoList =
+ fetchPageOffsetListByChunkOffset(valueChunkOffset);
+ valuePageInfoList.add(pageOffsetInfoList.get(positionInTimmeChunkPages));
+ }
+
+ reader.position(timeseriesIndexOffsetInfo.getOffset());
+ reader.readMarker();
+ PageHeader timePageHeader =
+ reader.readPageHeader(
+ timeseriesIndexOffsetInfo.getTsDataType(),
+ timeseriesIndexOffsetInfo.isHasStatistics());
+ ByteBuffer timeByteBuffer =
+ reader.readPage(timePageHeader, timeseriesIndexOffsetInfo.getCompressionType());
+
+ List<PageHeader> valuePageHeaders = new ArrayList<>();
+ List<ByteBuffer> valueByteBuffers = new ArrayList<>();
+ List<TSDataType> valueTSDataTypes = new ArrayList<>();
+ List<Decoder> valueDecoders = new ArrayList<>();
+
+ for (PageOffsetInfo valuePageInfo : valuePageInfoList) {
+ reader.position(valuePageInfo.getOffset());
+ reader.readMarker();
+ PageHeader valuePageHeader =
+ reader.readPageHeader(valuePageInfo.getTsDataType(), valuePageInfo.isHasStatistics());
+
+ valuePageHeaders.add(valuePageHeader);
+ valueByteBuffers.add(reader.readPage(valuePageHeader, valuePageInfo.getCompressionType()));
+ valueTSDataTypes.add(valuePageInfo.getTsDataType());
+ valueDecoders.add(
+ Decoder.getDecoderByType(
+ valuePageInfo.getEncodingType(), valuePageInfo.getTsDataType()));
+ }
+ AlignedPageReader alignedPageReader =
+ new AlignedPageReader(
+ timePageHeader,
+ timeByteBuffer,
+ timeDecoder,
+ valuePageHeaders,
+ valueByteBuffers,
+ valueTSDataTypes,
+ valueDecoders,
+ null);
+
+ batchData = alignedPageReader.getAllSatisfiedPageData();
+ }
+ tableInfo.setData(batchData);
+ return tableInfo;
+ }
+
+ /**
+ * **********************************************************for web
+ * clinet*************************************************************************************
+ */
+ public long getFileSize() {
+ return fileSize;
+ }
+
+ public boolean isSeq() {
+ return seq;
+ }
+
+ public String getVersion() {
+ return version;
+ }
+
+ public String getFilePath() {
+ return filePath;
+ }
+
+ public long getAllCount() {
+ return allCount;
+ }
+
+ /**
+ * 获取timeSeriesMetadataNode
+ *
+ * @return TimeSeriesMetadataNode
+ */
+ public TimeSeriesMetadataNode getTimeSeriesMetadataNode() throws InterruptedException {
+ countDownLatch.await();
+ return timeSeriesMetadataNode;
+ }
+
+ public TsFileSequenceReader getReader() {
+ return reader;
+ }
+
+ public double getRateOfProcess() {
+ return rateOfProcess;
+ }
+
+ public List<ChunkGroupMetadataModel> getChunkGroupMetadataModelList() {
+ return chunkGroupMetadataModelList;
+ }
+
+ public List<ChunkGroupInfo> getChunkGroupInfoList() {
+ return chunkGroupInfoList;
+ }
+
+ public Map<Long, Pair<Path, ITimeSeriesMetadata>> getTimeseriesMetadataMap() {
+ return timeseriesMetadataMap;
+ }
+
+ private class TsFileAnalysedToolReader extends TsFileSequenceReader {
+ public TsFileAnalysedToolReader(String file) throws IOException {
+ super(file);
+ }
+
+ /**
+ * Traverse the metadata index from MetadataIndexEntry to get TimeseriesMetadatas
+ *
+ * @param metadataIndex MetadataIndexEntry
+ * @param buffer byte buffer
+ * @param deviceId String
+ * @param timeseriesMetadataMap map: deviceId -> timeseriesMetadata list
+ * @param tsNode TimeSeriesMetadataNode
+ * @param needChunkMetadata deserialize chunk metadata list or not
+ */
+ private void generateMetadataIndexWithOffset(
+ long startOffset,
+ MetadataIndexEntry metadataIndex,
+ ByteBuffer buffer,
+ String deviceId,
+ MetadataIndexNodeType type,
+ Map<Long, Pair<Path, ITimeSeriesMetadata>> timeseriesMetadataMap,
+ TimeSeriesMetadataNode tsNode,
+ boolean needChunkMetadata)
+ throws IOException {
+ try {
+ if (type.equals(MetadataIndexNodeType.LEAF_MEASUREMENT)) {
+ TimeseriesMetadata alignedTime = null;
+ List<TimeseriesMetadata> alignedValues = new ArrayList<>();
+ AlignedTimeSeriesMetadata alignedTimeSeriesMetadata = null;
+ boolean aligned = true;
+ while (buffer.hasRemaining()) {
+ long pos = startOffset + buffer.position();
+ TimeseriesMetadata timeseriesMetadata =
+ TimeseriesMetadata.deserializeFrom(buffer, needChunkMetadata);
+ // 判断是否对其时间序列
+ if ((timeseriesMetadata.getTimeSeriesMetadataType() & TsFileConstant.TIME_COLUMN_MASK)
+ == TsFileConstant.TIME_COLUMN_MASK) {
+ if (aligned && alignedTime != null && alignedValues.size() > 0) {
+ leafNodeWithAligned(
+ alignedTime,
+ alignedValues,
+ startOffset + buffer.position(),
+ deviceId,
+ tsNode,
+ type,
+ timeseriesMetadataMap);
+ }
+ aligned = true;
+ alignedTime = new TimeseriesMetadata(timeseriesMetadata);
+ } else if ((timeseriesMetadata.getTimeSeriesMetadataType()
+ & TsFileConstant.VALUE_COLUMN_MASK)
+ == TsFileConstant.VALUE_COLUMN_MASK) {
+ alignedValues.add(timeseriesMetadata);
+ } else {
+
+ if (aligned && alignedTime != null && alignedValues.size() > 0) {
+ leafNodeWithAligned(
+ alignedTime,
+ alignedValues,
+ startOffset + buffer.position(),
+ deviceId,
+ tsNode,
+ type,
+ timeseriesMetadataMap);
+ }
+
+ timeseriesMetadataMap.put(
+ pos,
+ new Pair<>(
+ new Path(deviceId, timeseriesMetadata.getMeasurementId()),
+ timeseriesMetadata));
+ TimeSeriesMetadataNode leafNode = new TimeSeriesMetadataNode();
+ leafNode.setChildren(new ArrayList<>());
+ leafNode.setPosition(pos);
+ leafNode.setDeviceId(deviceId);
+ leafNode.setMeasurementId(timeseriesMetadata.getMeasurementId());
+ leafNode.setTimeseriesMetadata(timeseriesMetadata);
+ allCount += timeseriesMetadata.getStatistics().getCount();
+ leafNode.setNodeType(type);
+ tsNode.getChildren().add(leafNode);
+ aligned = false;
+ }
+ }
+ if (aligned && alignedTime != null && alignedValues.size() > 0) {
+ leafNodeWithAligned(
+ alignedTime,
+ alignedValues,
+ startOffset + buffer.position(),
+ deviceId,
+ tsNode,
+ type,
+ timeseriesMetadataMap);
+ }
+
+ tsNode.setNodeType(type);
+ tsNode.setPosition(startOffset + buffer.position());
+ tsNode.setDeviceId(deviceId);
+ } else {
+ // deviceId should be determined by LEAF_DEVICE node
+ if (type.equals(MetadataIndexNodeType.LEAF_DEVICE)) {
+ deviceId = metadataIndex.getName();
+ }
+ MetadataIndexNode metadataIndexNode = MetadataIndexNode.deserializeFrom(buffer);
+ int metadataIndexListSize = metadataIndexNode.getChildren().size();
+ for (int i = 0; i < metadataIndexListSize; i++) {
+ long endOffset = metadataIndexNode.getEndOffset();
+ if (i != metadataIndexListSize - 1) {
+ endOffset = metadataIndexNode.getChildren().get(i + 1).getOffset();
+ }
+ ByteBuffer nextBuffer =
+ readData(metadataIndexNode.getChildren().get(i).getOffset(), endOffset);
+ TimeSeriesMetadataNode tsChildNode = new TimeSeriesMetadataNode();
+ generateMetadataIndexWithOffset(
+ metadataIndexNode.getChildren().get(i).getOffset(),
+ metadataIndexNode.getChildren().get(i),
+ nextBuffer,
+ deviceId,
+ metadataIndexNode.getNodeType(),
+ timeseriesMetadataMap,
+ tsChildNode,
+ needChunkMetadata);
+ tsNode.getChildren().add(tsChildNode);
+ }
+ tsNode.setDeviceId(deviceId);
+ tsNode.setPosition(startOffset + buffer.position());
+ tsNode.setNodeType(type);
+ }
+ } catch (BufferOverflowException e) {
+ logger.error("Unrecognized metadataIndexNode type, type:{}", type);
+ throw e;
+ }
+ }
+
+ private void leafNodeWithAligned(
+ TimeseriesMetadata alignedTime,
+ List<TimeseriesMetadata> alignedValues,
+ long pos,
+ String deviceId,
+ TimeSeriesMetadataNode tsNode,
+ MetadataIndexNodeType type,
+ Map<Long, Pair<Path, ITimeSeriesMetadata>> timeseriesMetadataMap) {
+ if (alignedTime != null && alignedValues.size() > 0) {
+ AlignedTimeSeriesMetadata alignedTimeSeriesMetadata =
+ new AlignedTimeSeriesMetadata(alignedTime, new ArrayList<>(alignedValues));
+
+ TimeSeriesMetadataNode leafNode = new TimeSeriesMetadataNode();
+ leafNode.setChildren(new ArrayList<>());
+ leafNode.setPosition(pos);
+ leafNode.setDeviceId(deviceId);
+ StringBuilder measureIds = new StringBuilder();
+ for (TimeseriesMetadata valueMetadata : alignedValues) {
+ valueMetadata.getMeasurementId();
+ if (measureIds.length() == 0) {
+ measureIds.append(valueMetadata.getMeasurementId());
+ } else {
+ measureIds.append(" || ").append(valueMetadata.getMeasurementId());
+ }
+ }
+ leafNode.setMeasurementId(measureIds.toString());
+ leafNode.setTimeseriesMetadata(alignedTimeSeriesMetadata);
+ leafNode.setAligned(true);
+ for (TimeseriesMetadata value : alignedValues) {
+ if (value != null && value.getStatistics() != null) {
+ allCount += value.getStatistics().getCount();
+ }
+ }
+
+ timeseriesMetadataMap.put(
+ pos, new Pair<>(new Path(deviceId, measureIds.toString()), alignedTimeSeriesMetadata));
+
+ leafNode.setNodeType(type);
+ tsNode.getChildren().add(leafNode);
+ alignedValues.clear();
+ }
+ }
+
+ public TimeSeriesMetadataNode getAllTimeseriesMetadataWithOffset() throws IOException {
+ if (tsFileMetaData == null) {
+ readFileMetadata();
+ logger.info("Start reading TsFileMetadata, preparing to deserialized index.");
+ }
+ MetadataIndexNode metadataIndexNode = tsFileMetaData.getMetadataIndex();
+ TimeSeriesMetadataNode node = new TimeSeriesMetadataNode();
+ List<MetadataIndexEntry> metadataIndexEntryList = metadataIndexNode.getChildren();
+ for (int i = 0; i < metadataIndexEntryList.size(); i++) {
+ rateOfProcess =
+ loadOfPercent
+ + parseOfPercent
+ + (i + 1) / metadataIndexEntryList.size() * indexOfPercent;
+ TimeSeriesMetadataNode entry = new TimeSeriesMetadataNode();
+ MetadataIndexEntry metadataIndexEntry = metadataIndexEntryList.get(i);
+ long endOffset = tsFileMetaData.getMetadataIndex().getEndOffset();
+ if (i != metadataIndexEntryList.size() - 1) {
+ endOffset = metadataIndexEntryList.get(i + 1).getOffset();
+ }
+ ByteBuffer buffer = readData(metadataIndexEntry.getOffset(), endOffset);
+ generateMetadataIndexWithOffset(
+ metadataIndexEntry.getOffset(),
+ metadataIndexEntry,
+ buffer,
+ null,
+ metadataIndexNode.getNodeType(),
+ timeseriesMetadataMap,
+ entry,
+ true);
+ node.getChildren().add(entry);
+ }
+ node.setNodeType(metadataIndexNode.getNodeType());
+ node.setPosition(reader.getFileMetadataPos());
+
+ return node;
+ }
+
+ public ByteBuffer readDataFromReader(long start, int totalSize) throws IOException {
+ return readData(start, totalSize);
+ }
+ }
+}
diff --git a/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/service/TsFileValuePageReader.java b/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/service/TsFileValuePageReader.java
new file mode 100644
index 0000000..beab342
--- /dev/null
+++ b/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/service/TsFileValuePageReader.java
@@ -0,0 +1,118 @@
+/*
+ * 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.
+ */
+package org.apache.iotdb.tool.core.service;
+
+import org.apache.iotdb.tsfile.encoding.decoder.Decoder;
+import org.apache.iotdb.tsfile.exception.write.UnSupportedDataTypeException;
+import org.apache.iotdb.tsfile.file.header.PageHeader;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
+import org.apache.iotdb.tsfile.utils.Binary;
+import org.apache.iotdb.tsfile.utils.ReadWriteIOUtils;
+import org.apache.iotdb.tsfile.utils.TsPrimitiveType;
+
+import java.nio.ByteBuffer;
+
+public class TsFileValuePageReader {
+
+ private static final int MASK = 0x80;
+
+ private final PageHeader pageHeader;
+
+ private final TSDataType dataType;
+
+ /** decoder for value column */
+ private final Decoder valueDecoder;
+
+ private byte[] bitmap;
+
+ private int size;
+
+ /** value column in memory */
+ protected ByteBuffer valueBuffer;
+
+ public TsFileValuePageReader(
+ PageHeader pageHeader, ByteBuffer pageData, TSDataType dataType, Decoder valueDecoder) {
+ this.dataType = dataType;
+ this.valueDecoder = valueDecoder;
+ this.pageHeader = pageHeader;
+ if (pageData != null) {
+ splitDataToBitmapAndValue(pageData);
+ }
+ this.valueBuffer = pageData;
+ }
+
+ private void splitDataToBitmapAndValue(ByteBuffer pageData) {
+ if (!pageData.hasRemaining()) { // Empty Page
+ return;
+ }
+ this.size = ReadWriteIOUtils.readInt(pageData);
+ this.bitmap = new byte[(size + 7) / 8];
+ pageData.get(bitmap);
+ this.valueBuffer = pageData.slice();
+ }
+
+ /**
+ * return the value array of the corresponding time, if this sub sensor don't have a value in a
+ * time, just fill it with null
+ */
+ public TsPrimitiveType[] allValueBatch() {
+ TsPrimitiveType[] valueBatch = new TsPrimitiveType[size];
+ if (valueBuffer == null) {
+ return valueBatch;
+ }
+ for (int i = 0; i < size; i++) {
+ if (((bitmap[i / 8] & 0xFF) & (MASK >>> (i % 8))) == 0) {
+ continue;
+ }
+ switch (dataType) {
+ case BOOLEAN:
+ boolean aBoolean = valueDecoder.readBoolean(valueBuffer);
+ valueBatch[i] = new TsPrimitiveType.TsBoolean(aBoolean);
+ break;
+ case INT32:
+ int anInt = valueDecoder.readInt(valueBuffer);
+ valueBatch[i] = new TsPrimitiveType.TsInt(anInt);
+ break;
+ case INT64:
+ long aLong = valueDecoder.readLong(valueBuffer);
+ valueBatch[i] = new TsPrimitiveType.TsLong(aLong);
+ break;
+ case FLOAT:
+ float aFloat = valueDecoder.readFloat(valueBuffer);
+ valueBatch[i] = new TsPrimitiveType.TsFloat(aFloat);
+ break;
+ case DOUBLE:
+ double aDouble = valueDecoder.readDouble(valueBuffer);
+ valueBatch[i] = new TsPrimitiveType.TsDouble(aDouble);
+ break;
+ case TEXT:
+ Binary aBinary = valueDecoder.readBinary(valueBuffer);
+ valueBatch[i] = new TsPrimitiveType.TsBinary(aBinary);
+ break;
+ default:
+ throw new UnSupportedDataTypeException(String.valueOf(dataType));
+ }
+ }
+ return valueBatch;
+ }
+
+ public TSDataType getDataType() {
+ return dataType;
+ }
+}
diff --git a/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/util/OffLineTsFileUtil.java b/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/util/OffLineTsFileUtil.java
new file mode 100644
index 0000000..2acc859
--- /dev/null
+++ b/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/util/OffLineTsFileUtil.java
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+package org.apache.iotdb.tool.core.util;
+
+import org.apache.iotdb.tsfile.read.TsFileSequenceReader;
+
+import java.io.IOException;
+
+public class OffLineTsFileUtil {
+
+ public static int fetchTsFileVersionNumber(String filePath) throws IOException {
+ try (TsFileSequenceReader reader = new TsFileSequenceReader(filePath, false)) {
+ return reader.readVersionNumber();
+ }
+ }
+}
diff --git a/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/util/TsFileEncodeCompressAnalysedUtil.java b/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/util/TsFileEncodeCompressAnalysedUtil.java
new file mode 100644
index 0000000..5791fcb
--- /dev/null
+++ b/tsfile-viewer-core/src/main/java/org/apache/iotdb/tool/core/util/TsFileEncodeCompressAnalysedUtil.java
@@ -0,0 +1,422 @@
+/*
+ * 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.
+ */
+
+package org.apache.iotdb.tool.core.util;
+
+import org.apache.iotdb.tool.core.model.DsTypeEncodeModel;
+import org.apache.iotdb.tool.core.model.EncodeCompressAnalysedModel;
+import org.apache.iotdb.tsfile.compress.ICompressor;
+import org.apache.iotdb.tsfile.encoding.encoder.*;
+import org.apache.iotdb.tsfile.file.metadata.enums.CompressionType;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSEncoding;
+import org.apache.iotdb.tsfile.read.common.BatchData;
+import org.apache.iotdb.tsfile.utils.Binary;
+import org.apache.iotdb.tsfile.utils.PublicBAOS;
+import org.apache.iotdb.tsfile.utils.TsPrimitiveType;
+
+import java.io.IOException;
+import java.util.*;
+import java.util.stream.Collectors;
+
+public class TsFileEncodeCompressAnalysedUtil {
+ /** support compression type array */
+ private static final CompressionType[] compressTypes =
+ new CompressionType[] {
+ CompressionType.SNAPPY,
+ CompressionType.GZIP,
+ CompressionType.LZ4,
+ CompressionType.UNCOMPRESSED
+ };
+
+ /** compressed rate weight */
+ private static final double compressedWeight = 2.5;
+
+ /** compressed sequence weight */
+ private static final double compressedSequenceWeight = 2.5;
+
+ /** compressed cost weight */
+ private static final double compressedCostWeight = 5;
+
+ /** score rate */
+ private static final double zeroRate = 0.8;
+
+ /**
+ * generate encode and compress analysed with batchData
+ *
+ * @param batchData batch data
+ * @return EncodeCompressAnalysedModel list
+ * @throws IOException throw io exception
+ */
+ public static List<EncodeCompressAnalysedModel> generateEncodeAndCompressAnalysedWithBatchData(
+ BatchData batchData) throws IOException {
+ //
+ DsTypeEncodeModel encodeModel = generateDsTypeEncodeModel(batchData.getDataType());
+ if (encodeModel == null) {
+ return null;
+ }
+ List<Encoder> encoders = encodeModel.getEncoders();
+ List<PublicBAOS> publicBAOS = encodeModel.getPublicBAOS();
+ while (batchData.hasCurrent()) {
+ tsPrimitiveTypeEncode(batchData.currentTsPrimitiveType(), encoders, publicBAOS);
+ batchData.next();
+ }
+ return generateEncodeAndCompressAnalysedBase(encodeModel);
+ }
+
+ /**
+ * generate encode and compress analysed with tsPrimitiveType array
+ *
+ * @param tsPrimitiveTypes tsPrimitiveType array
+ * @return EncodeCompressAnalysedModel list
+ * @throws IOException throw io exception
+ */
+ public static List<EncodeCompressAnalysedModel> generateEncodeAndCompressAnalysedWithTsPrimitives(
+ TsPrimitiveType[] tsPrimitiveTypes) throws IOException {
+ //
+ DsTypeEncodeModel encodeModel = generateDsTypeEncodeModel(tsPrimitiveTypes[0].getDataType());
+ if (encodeModel == null) {
+ return null;
+ }
+ List<Encoder> encoders = encodeModel.getEncoders();
+ List<PublicBAOS> publicBAOS = encodeModel.getPublicBAOS();
+ for (int i = 0; i < tsPrimitiveTypes.length; i++) {
+ tsPrimitiveTypeEncode(tsPrimitiveTypes[i], encoders, publicBAOS);
+ }
+ return generateEncodeAndCompressAnalysedBase(encodeModel);
+ }
+
+ /**
+ * generate encode and compress analysed base method
+ *
+ * @param encodeModel encode model
+ * @return EncodeCompressAnalysedModel list
+ * @throws IOException throw io exception
+ */
+ private static List<EncodeCompressAnalysedModel> generateEncodeAndCompressAnalysedBase(
+ DsTypeEncodeModel encodeModel) throws IOException {
+ List<PublicBAOS> publicBAOS = encodeModel.getPublicBAOS();
+ List<String> encodeNameList = encodeModel.getEncodeNameList();
+ List<EncodeCompressAnalysedModel> modelList = new ArrayList<>();
+ List<Encoder> encoders = encodeModel.getEncoders();
+ for (int i = 0; i < encodeModel.getEncoders().size(); i++) {
+ encoders.get(i).flush(publicBAOS.get(i));
+ }
+ long uncompressSize = publicBAOS.get(0).size();
+ for (int i = 0; i < encodeModel.getEncoders().size(); i++) {
+ for (int j = 0; j < compressTypes.length; j++) {
+ modelList.add(
+ generateAnalysedModel(
+ compressTypes[j],
+ encodeNameList.get(i),
+ uncompressSize,
+ encodeModel.getTypeName(),
+ publicBAOS.get(i)));
+ }
+ }
+ for (PublicBAOS baos : publicBAOS) {
+ baos.close();
+ }
+ return modelList;
+ }
+
+ /**
+ * encode tsPrimitiveType data
+ *
+ * @param tsPrimitiveType tsPrimitiveType
+ * @param encoders encode list
+ * @param publicBAOS publicBAOS list
+ */
+ private static void tsPrimitiveTypeEncode(
+ TsPrimitiveType tsPrimitiveType, List<Encoder> encoders, List<PublicBAOS> publicBAOS) {
+ switch (tsPrimitiveType.getDataType()) {
+ case INT64:
+ long longValue = tsPrimitiveType.getLong();
+ for (int i = 0; i < encoders.size(); i++) {
+ encoders.get(i).encode(longValue, publicBAOS.get(i));
+ }
+ return;
+ case INT32:
+ int intValue = tsPrimitiveType.getInt();
+ for (int i = 0; i < encoders.size(); i++) {
+ encoders.get(i).encode(intValue, publicBAOS.get(i));
+ }
+ return;
+ case FLOAT:
+ float floatValue = tsPrimitiveType.getFloat();
+ for (int i = 0; i < encoders.size(); i++) {
+ encoders.get(i).encode(floatValue, publicBAOS.get(i));
+ }
+ return;
+ case DOUBLE:
+ double doubleValue = tsPrimitiveType.getDouble();
+ for (int i = 0; i < encoders.size(); i++) {
+ encoders.get(i).encode(doubleValue, publicBAOS.get(i));
+ }
+ return;
+ case TEXT:
+ Binary textValue = tsPrimitiveType.getBinary();
+ for (int i = 0; i < encoders.size(); i++) {
+ encoders.get(i).encode(textValue, publicBAOS.get(i));
+ }
+ return;
+ default:
+ }
+ }
+
+ /**
+ * generate dsTypeEncodeModel
+ *
+ * @param dataType data type
+ * @return dsTypeEncodeModel
+ */
+ private static DsTypeEncodeModel generateDsTypeEncodeModel(TSDataType dataType) {
+
+ switch (dataType) {
+ case INT64:
+ return generateLongEncodeModel(dataType.name());
+ case INT32:
+ return generateIntEncodeModel(dataType.name());
+ case FLOAT:
+ return generateFloatEncodeModel(dataType.name());
+ case DOUBLE:
+ return generateDoubleEncodeModel(dataType.name());
+ case TEXT:
+ return generateTextEncodeModel(dataType.name());
+ default:
+ return null;
+ }
+ }
+
+ /**
+ * generate int encode model
+ *
+ * @param typeName int type name
+ * @return int dsTypeEncodeModel
+ */
+ private static DsTypeEncodeModel generateIntEncodeModel(String typeName) {
+ List<String> encodeNameList = new ArrayList<>();
+ encodeNameList.add(TSEncoding.PLAIN.name());
+ encodeNameList.add(TSEncoding.GORILLA.name());
+ encodeNameList.add(TSEncoding.RLE.name());
+ encodeNameList.add(TSEncoding.TS_2DIFF.name());
+ PlainEncoder plainEncoder = new PlainEncoder(TSDataType.INT32, 128);
+ IntGorillaEncoder gorillaEncoder = new IntGorillaEncoder();
+ IntRleEncoder rleEncoder = new IntRleEncoder();
+ DeltaBinaryEncoder.IntDeltaEncoder deltaEncoder = new DeltaBinaryEncoder.IntDeltaEncoder();
+ List<Encoder> encoders = new ArrayList<>();
+ encoders.add(plainEncoder);
+ encoders.add(gorillaEncoder);
+ encoders.add(rleEncoder);
+ encoders.add(deltaEncoder);
+ return generateEncodeModel(typeName, encoders, encodeNameList);
+ }
+
+ /**
+ * generate long encode model
+ *
+ * @param typeName long type name
+ * @return long type dsTypeEncodeModel
+ */
+ private static DsTypeEncodeModel generateLongEncodeModel(String typeName) {
+
+ List<String> encodeNameList = new ArrayList<>();
+ encodeNameList.add(TSEncoding.PLAIN.name());
+ encodeNameList.add(TSEncoding.GORILLA.name());
+ encodeNameList.add(TSEncoding.RLE.name());
+ encodeNameList.add(TSEncoding.TS_2DIFF.name());
+ PlainEncoder plainEncoder = new PlainEncoder(TSDataType.INT64, 128);
+ LongGorillaEncoder gorillaEncoder = new LongGorillaEncoder();
+ LongRleEncoder rleEncoder = new LongRleEncoder();
+ DeltaBinaryEncoder.LongDeltaEncoder deltaEncoder = new DeltaBinaryEncoder.LongDeltaEncoder();
+ List<Encoder> encoders = new ArrayList<>();
+ encoders.add(plainEncoder);
+ encoders.add(gorillaEncoder);
+ encoders.add(rleEncoder);
+ encoders.add(deltaEncoder);
+ return generateEncodeModel(typeName, encoders, encodeNameList);
+ }
+
+ /**
+ * generate float encode model
+ *
+ * @param typeName float type name
+ * @return dsTypeEncodeModel
+ */
+ private static DsTypeEncodeModel generateFloatEncodeModel(String typeName) {
+ List<String> encodeNameList = new ArrayList<>();
+ encodeNameList.add(TSEncoding.PLAIN.name());
+ encodeNameList.add(TSEncoding.GORILLA.name());
+ PlainEncoder plainEncoder = new PlainEncoder(TSDataType.FLOAT, 128);
+ SinglePrecisionEncoderV2 gorillaEncoder = new SinglePrecisionEncoderV2();
+ List<Encoder> encoders = new ArrayList<>();
+ encoders.add(plainEncoder);
+ encoders.add(gorillaEncoder);
+ return generateEncodeModel(typeName, encoders, encodeNameList);
+ }
+
+ /**
+ * generate double encode model
+ *
+ * @param typeName double type name
+ * @return dsTypeEncodeModel
+ */
+ private static DsTypeEncodeModel generateDoubleEncodeModel(String typeName) {
+ List<String> encodeNameList = new ArrayList<>();
+ encodeNameList.add(TSEncoding.PLAIN.name());
+ encodeNameList.add(TSEncoding.GORILLA.name());
+ PlainEncoder plainEncoder = new PlainEncoder(TSDataType.DOUBLE, 128);
+ DoublePrecisionEncoderV2 gorillaEncoder = new DoublePrecisionEncoderV2();
+
+ List<Encoder> encoders = new ArrayList<>();
+ encoders.add(plainEncoder);
+ encoders.add(gorillaEncoder);
+
+ return generateEncodeModel(typeName, encoders, encodeNameList);
+ }
+
+ /**
+ * generate text encode model
+ *
+ * @param typeName text type name
+ * @return dsTypeEncodeModel
+ */
+ private static DsTypeEncodeModel generateTextEncodeModel(String typeName) {
+ List<String> encodeNameList = new ArrayList<>();
+ encodeNameList.add(TSEncoding.PLAIN.name());
+ encodeNameList.add(TSEncoding.DICTIONARY.name());
+ PlainEncoder plainEncoder = new PlainEncoder(TSDataType.TEXT, 128);
+ DictionaryEncoder dictionaryEncoder = new DictionaryEncoder();
+ List<Encoder> encoders = new ArrayList<>();
+ encoders.add(plainEncoder);
+ encoders.add(dictionaryEncoder);
+ return generateEncodeModel(typeName, encoders, encodeNameList);
+ }
+
+ /**
+ * @param typeName type name
+ * @param encoders encode list
+ * @param encodeNameList encodeName list
+ * @return dsTypeEncodeModel
+ */
+ private static DsTypeEncodeModel generateEncodeModel(
+ String typeName, List<Encoder> encoders, List<String> encodeNameList) {
+ DsTypeEncodeModel model = new DsTypeEncodeModel();
+ model.setTypeName(typeName);
+
+ List<PublicBAOS> baos = new ArrayList<>();
+ for (int i = 0; i < encoders.size(); i++) {
+ baos.add(new PublicBAOS());
+ }
+ model.setEncodeNameList(encodeNameList);
+ model.setEncoders(encoders);
+ model.setPublicBAOS(baos);
+ return model;
+ }
+
+ /**
+ * 组装EncodeCompressAnalysedModel模型
+ *
+ * @param compressionType compress type
+ * @param encodeName encode name
+ * @param originSize origin size
+ * @param typeName type name
+ * @param baos baos
+ * @return encode compress analysed model
+ * @throws IOException throw io exception
+ */
+ private static EncodeCompressAnalysedModel generateAnalysedModel(
+ CompressionType compressionType,
+ String encodeName,
+ long originSize,
+ String typeName,
+ PublicBAOS baos)
+ throws IOException {
+ ICompressor compressor;
+ if (compressionType.equals(CompressionType.SNAPPY)) {
+ compressor = new ICompressor.SnappyCompressor();
+ } else if (compressionType.equals(CompressionType.GZIP)) {
+ compressor = new ICompressor.GZIPCompressor();
+ } else if (compressionType.equals(CompressionType.LZ4)) {
+ compressor = new ICompressor.IOTDBLZ4Compressor();
+ } else {
+ compressor = new ICompressor.NoCompressor();
+ }
+ long startTime = System.nanoTime();
+ long compressedSize = compressor.compress(baos.getBuf()).length;
+ long compressedCost = System.nanoTime() - startTime;
+ EncodeCompressAnalysedModel model = new EncodeCompressAnalysedModel();
+ model.setCompressName(compressionType.name());
+ model.setCompressedSize(compressedSize);
+ model.setUncompressSize(baos.size());
+ model.setTypeName(typeName);
+ model.setEncodeName(encodeName);
+ model.setEncodedSize(baos.size());
+ model.setOriginSize(originSize);
+ model.setCompressedCost(compressedCost);
+ return model;
+ }
+
+ /**
+ * sorted analysed model
+ *
+ * @param map encodeCompressAnalysedModel map
+ * @return EncodeCompressAnalysedModel list
+ */
+ public static List<EncodeCompressAnalysedModel> sortedAnalysedModel(
+ Map<String, EncodeCompressAnalysedModel> map) {
+ List<EncodeCompressAnalysedModel> sortedCostModels =
+ map.values().stream()
+ .sorted(Comparator.comparing(EncodeCompressAnalysedModel::getCompressedCost))
+ .collect(Collectors.toList());
+ List<EncodeCompressAnalysedModel> sortedCompressedModels =
+ map.values().stream()
+ .sorted(Comparator.comparing(EncodeCompressAnalysedModel::getCompressedSize))
+ .collect(Collectors.toList());
+ Map<String, EncodeCompressAnalysedModel> scoresMap = new HashMap<>();
+ // 计算压缩得分
+ for (int i = 0; i < sortedCompressedModels.size(); i++) {
+ EncodeCompressAnalysedModel model = sortedCompressedModels.get(i);
+ double compressedScores =
+ compressedWeight * (1 - (double) model.getCompressedSize() / model.getOriginSize());
+ double sequenceScores = 0;
+ double rate = (double) i / sortedCostModels.size();
+ if (rate < zeroRate) {
+ sequenceScores = compressedSequenceWeight * (1 - rate);
+ }
+ model.setScore(compressedScores + sequenceScores);
+ String key = model.getCompressName() + "-" + model.getEncodeName();
+ scoresMap.put(key, model);
+ }
+ // 计算耗时得分
+ for (int i = 0; i < sortedCostModels.size(); i++) {
+ EncodeCompressAnalysedModel model = sortedCostModels.get(i);
+ double sequenceScores = 0;
+ double rate = (double) i / sortedCostModels.size();
+ if (rate < zeroRate) {
+ sequenceScores = compressedCostWeight * (1 - rate);
+ }
+ String key = model.getCompressName() + "-" + model.getEncodeName();
+ scoresMap.get(key).setScore(scoresMap.get(key).getScore() + sequenceScores);
+ }
+ return scoresMap.values().stream()
+ .sorted(Comparator.comparing(EncodeCompressAnalysedModel::getScore).reversed())
+ .collect(Collectors.toList());
+ }
+}
diff --git a/tsfile-viewer-core/src/test/java/org/apache/iotdb/tool/core/service/TsFileAnalyserV13Test.java b/tsfile-viewer-core/src/test/java/org/apache/iotdb/tool/core/service/TsFileAnalyserV13Test.java
new file mode 100644
index 0000000..09399bb
--- /dev/null
+++ b/tsfile-viewer-core/src/test/java/org/apache/iotdb/tool/core/service/TsFileAnalyserV13Test.java
@@ -0,0 +1,181 @@
+/*
+ * 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.
+ */
+
+package org.apache.iotdb.tool.core.service;
+
+import org.apache.iotdb.tsfile.common.constant.TsFileConstant;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSDataType;
+import org.apache.iotdb.tsfile.file.metadata.enums.TSEncoding;
+import org.apache.iotdb.tsfile.file.metadata.statistics.Statistics;
+import org.apache.iotdb.tsfile.read.common.Path;
+import org.apache.iotdb.tsfile.utils.MeasurementGroup;
+import org.apache.iotdb.tsfile.write.schema.MeasurementSchema;
+import org.apache.iotdb.tsfile.write.schema.Schema;
+import org.apache.iotdb.tsfile.write.schema.VectorMeasurementSchema;
+import org.apache.iotdb.tsfile.write.writer.TsFileIOWriter;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class TsFileAnalyserV13Test {
+
+ private static final String FILE_PATH = "TsFileIOWriterTest.tsfile";
+ private static final String DEVICE_1 = "device1";
+ private static final String DEVICE_2 = "device2";
+ private static final String SENSOR_1 = "sensor1";
+
+ private static final int CHUNK_GROUP_NUM = 2;
+
+ @Before
+ public void before() throws IOException {
+ TsFileIOWriter writer = new TsFileIOWriter(new File(FILE_PATH));
+
+ // file schema
+ MeasurementSchema measurementSchema = createSimpleMeasurementSchema(SENSOR_1);
+ VectorMeasurementSchema vectorMeasurementSchema =
+ new VectorMeasurementSchema(
+ "", new String[] {"s1", "s2"}, new TSDataType[] {TSDataType.INT64, TSDataType.INT64});
+ List<MeasurementSchema> schemas = new ArrayList<>();
+ schemas.add(new MeasurementSchema("s1", TSDataType.INT64, TSEncoding.RLE));
+ schemas.add(new MeasurementSchema("s2", TSDataType.INT64, TSEncoding.RLE));
+ MeasurementGroup group = new MeasurementGroup(true, schemas);
+
+ Schema schema = new Schema();
+ schema.registerTimeseries(new Path(DEVICE_1), measurementSchema);
+ schema.registerMeasurementGroup(new Path(DEVICE_2), group);
+
+ writeChunkGroup(writer, measurementSchema);
+ writeVectorChunkGroup(writer, vectorMeasurementSchema);
+ writer.setMinPlanIndex(100);
+ writer.setMaxPlanIndex(10000);
+ writer.writePlanIndices();
+ // end file
+ writer.endFile();
+ }
+
+ private MeasurementSchema createSimpleMeasurementSchema(String measurementuid) {
+ return new MeasurementSchema(measurementuid, TSDataType.INT64, TSEncoding.RLE);
+ }
+
+ @After
+ public void after() {
+ File file = new File(FILE_PATH);
+ if (file.exists()) {
+ file.delete();
+ }
+ }
+
+ @Test
+ public void testTsFileAnalyserV13ForAligned() throws IOException, InterruptedException {
+
+ TsFileAnalyserV13 tsFileAnalyserV13 = new TsFileAnalyserV13(FILE_PATH);
+ tsFileAnalyserV13.getTimeSeriesMetadataNode();
+ // 是否有序
+ assertTrue(tsFileAnalyserV13.isSeq());
+ // 进度条完成
+ assertEquals(1.0, tsFileAnalyserV13.getRateOfProcess(), 0);
+ // 是否AlignedChunkMetadata
+ assertTrue(tsFileAnalyserV13.getChunkGroupInfoList().get(3).getDeviceName().equals(DEVICE_2));
+ // 判断node节点
+ assertTrue(tsFileAnalyserV13.getTimeSeriesMetadataNode().getChildren().size() > 0);
+ }
+
+ @Test
+ public void testTsFileAnalyserV13ForNonAligned() throws IOException, InterruptedException {
+
+ TsFileAnalyserV13 tsFileAnalyserV13 = new TsFileAnalyserV13(FILE_PATH);
+ tsFileAnalyserV13.getTimeSeriesMetadataNode();
+ // 是否有序
+ assertTrue(tsFileAnalyserV13.isSeq());
+ // 进度条完成
+ assertEquals(1.0, tsFileAnalyserV13.getRateOfProcess(), 0);
+ // 是否AlignedChunkMetadata
+ assertTrue(tsFileAnalyserV13.getChunkGroupInfoList().get(0).getDeviceName().equals(DEVICE_1));
+ // 判断node节点
+ assertTrue(tsFileAnalyserV13.getTimeSeriesMetadataNode().getChildren().size() > 0);
+ }
+
+ private void writeChunkGroup(TsFileIOWriter writer, MeasurementSchema measurementSchema)
+ throws IOException {
+ for (int i = 0; i < CHUNK_GROUP_NUM; i++) {
+ // chunk group
+ writer.startChunkGroup(DEVICE_1);
+ // ordinary chunk, chunk statistics
+ Statistics statistics = Statistics.getStatsByType(measurementSchema.getType());
+ statistics.updateStats(0L, 0L);
+ writer.startFlushChunk(
+ measurementSchema.getMeasurementId(),
+ measurementSchema.getCompressor(),
+ measurementSchema.getType(),
+ measurementSchema.getEncodingType(),
+ statistics,
+ 0,
+ 0,
+ 0);
+ writer.endCurrentChunk();
+ writer.endChunkGroup();
+ }
+ }
+
+ private void writeVectorChunkGroup(
+ TsFileIOWriter writer, VectorMeasurementSchema vectorMeasurementSchema) throws IOException {
+ for (int i = 0; i < CHUNK_GROUP_NUM; i++) {
+ // chunk group
+ writer.startChunkGroup(DEVICE_2);
+ // vector chunk (time)
+ writer.startFlushChunk(
+ vectorMeasurementSchema.getMeasurementId(),
+ vectorMeasurementSchema.getCompressor(),
+ vectorMeasurementSchema.getType(),
+ vectorMeasurementSchema.getTimeTSEncoding(),
+ Statistics.getStatsByType(vectorMeasurementSchema.getType()),
+ 0,
+ 0,
+ TsFileConstant.TIME_COLUMN_MASK);
+ writer.endCurrentChunk();
+ // vector chunk (values)
+ for (int j = 0; j < vectorMeasurementSchema.getSubMeasurementsCount(); j++) {
+ Statistics subStatistics =
+ Statistics.getStatsByType(
+ vectorMeasurementSchema.getSubMeasurementsTSDataTypeList().get(j));
+ subStatistics.updateStats(0L, 0L);
+ writer.startFlushChunk(
+ vectorMeasurementSchema.getSubMeasurementsList().get(j),
+ vectorMeasurementSchema.getCompressor(),
+ vectorMeasurementSchema.getSubMeasurementsTSDataTypeList().get(j),
+ vectorMeasurementSchema.getSubMeasurementsTSEncodingList().get(j),
+ subStatistics,
+ 0,
+ 0,
+ TsFileConstant.VALUE_COLUMN_MASK);
+ writer.endCurrentChunk();
+ }
+ writer.endChunkGroup();
+ }
+ }
+}
diff --git a/tsfile-viewer-web-frontend/config/config.dev.js b/tsfile-viewer-web-frontend/config/config.dev.js
new file mode 100644
index 0000000..2187364
--- /dev/null
+++ b/tsfile-viewer-web-frontend/config/config.dev.js
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+// https://umijs.org/config/
+import { defineConfig } from 'umi';
+export default defineConfig({
+ plugins: [
+ // https://github.com/zthxxx/react-dev-inspector
+ 'react-dev-inspector/plugins/umi/react-inspector',
+ ],
+ // https://github.com/zthxxx/react-dev-inspector#inspector-loader-props
+ inspectorConfig: {
+ exclude: [],
+ babelPlugins: [],
+ babelOptions: {},
+ },
+});
diff --git a/tsfile-viewer-web-frontend/config/config.js b/tsfile-viewer-web-frontend/config/config.js
new file mode 100644
index 0000000..1d49193
--- /dev/null
+++ b/tsfile-viewer-web-frontend/config/config.js
@@ -0,0 +1,145 @@
+/*
+ * 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.
+ */
+// https://umijs.org/config/
+import { defineConfig } from 'umi';
+import { join } from 'path';
+import defaultSettings from './defaultSettings';
+import proxy from './proxy';
+const { REACT_APP_ENV } = process.env;
+export default defineConfig({
+ hash: true,
+ antd: {},
+ dva: {
+ hmr: true,
+ },
+ layout: {
+ // https://umijs.org/zh-CN/plugins/plugin-layout
+ locale: true,
+ siderWidth: 208,
+ ...defaultSettings,
+ },
+ // https://umijs.org/zh-CN/plugins/plugin-locale
+ locale: {
+ // default zh-CN
+ default: 'zh-CN',
+ antd: true,
+ // default true, when it is true, will use `navigator.language` overwrite default
+ baseNavigator: true,
+ },
+ dynamicImport: {
+ loading: '@ant-design/pro-layout/es/PageLoading',
+ },
+ targets: {
+ ie: 11,
+ },
+ // umi routes: https://umijs.org/docs/routing
+ routes: [
+ {
+ path: '/tsfile-tool/v2/',
+ name: 'tsfile-tool',
+ icon: 'FileOutlined',
+ component: './tsfile-tool/v2',
+ },
+ {
+ name: 'exception',
+ icon: 'warning',
+ path: '/exception',
+ hideInMenu: true,
+ routes: [
+ {
+ name: '403',
+ icon: 'smile',
+ path: '/exception/403',
+ component: './exception/403',
+ },
+ {
+ name: '404',
+ icon: 'smile',
+ path: '/exception/404',
+ component: './exception/404',
+ },
+ {
+ name: '500',
+ icon: 'smile',
+ path: '/exception/500',
+ component: './exception/500',
+ },
+ ],
+ },
+ {
+ component: '404',
+ },
+ ],
+ // Theme for antd: https://ant.design/docs/react/customize-theme-cn
+ theme: {
+ 'primary-color': defaultSettings.primaryColor,
+ },
+ // esbuild is father build tools
+ // https://umijs.org/plugins/plugin-esbuild
+ esbuild: {},
+ title: false,
+ ignoreMomentLocale: true,
+ // proxy: proxy[REACT_APP_ENV || 'dev'],
+ proxy: {
+ '/api': { // 标识需要进行转换的请求的url
+ "target": "http://localhost:8080/api/", // 服务端域名
+ "changeOrigin": true, // 允许域名进行转换
+ "pathRewrite": { "^/api": ''} // 将请求url里的ci去掉
+ },
+ },
+ manifest: {
+ basePath: '/',
+ },
+ // Fast Refresh 热更新
+ fastRefresh: {},
+ openAPI: [
+ {
+ requestLibPath: "import { request } from 'umi'",
+ // 或者使用在线的版本
+ // schemaPath: "https://gw.alipayobjects.com/os/antfincdn/M%24jrzTTYJN/oneapi.json"
+ schemaPath: join(__dirname, 'oneapi.json'),
+ projectName: 'ant-design-pro',
+ mock: false,
+ },
+ // {
+ // requestLibPath: "import { request } from 'umi'",
+ // schemaPath: 'https://gw.alipayobjects.com/os/antfincdn/CA1dOm%2631B/openapi.json',
+ // projectName: 'swagger',
+ // },
+ {
+ requestLibPath: "import { request } from 'umi'",
+ // schemaPath: join(__dirname, 'swagger.json'),
+ schemaPath: 'http://localhost:8080/v2/api-docs',
+ projectName: 'swagger1',
+ mock: false,
+ },
+ // {
+ // requestLibPath: "import { request } from 'umi'",
+ // // schemaPath: join(__dirname, 'swagger.json'),
+ // schemaPath: 'http://localhost:8081/api/spec',
+ // projectName: 'swagger2',
+ // mock: false,
+ // },
+ ],
+ nodeModulesTransform: {
+ type: 'none',
+ },
+ // mfsu: {},
+ webpack5: {},
+ exportStatic: {},
+ history: { type: 'hash' },
+});
diff --git a/tsfile-viewer-web-frontend/config/defaultSettings.js b/tsfile-viewer-web-frontend/config/defaultSettings.js
new file mode 100644
index 0000000..f7e2747
--- /dev/null
+++ b/tsfile-viewer-web-frontend/config/defaultSettings.js
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+const Settings = {
+ navTheme: 'light',
+ // blue: '#1890ff',
+ // red: '#FC4C2F',
+ primaryColor: '#FC4C2F',
+ layout: 'mix',
+ contentWidth: 'Fluid',
+ fixedHeader: false,
+ fixSiderbar: true,
+ colorWeak: false,
+ title: 'Tsfile Viewer',
+ pwa: false,
+ logo: "/iotdb-logo.png",
+ iconfontUrl: '',
+};
+export default Settings;
diff --git a/tsfile-viewer-web-frontend/config/oneapi.json b/tsfile-viewer-web-frontend/config/oneapi.json
new file mode 100644
index 0000000..95e6022
--- /dev/null
+++ b/tsfile-viewer-web-frontend/config/oneapi.json
@@ -0,0 +1,7 @@
+{
+ "openapi": "3.0.1",
+ "info": {
+ "title": "Ant Design Pro",
+ "version": "1.0.0"
+ }
+}
diff --git a/tsfile-viewer-web-frontend/config/proxy.js b/tsfile-viewer-web-frontend/config/proxy.js
new file mode 100644
index 0000000..bd6348d
--- /dev/null
+++ b/tsfile-viewer-web-frontend/config/proxy.js
@@ -0,0 +1,53 @@
+/*
+ * 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.
+ */
+/**
+ * 在生产环境 代理是无法生效的,所以这里没有生产环境的配置
+ * -------------------------------
+ * The agent cannot take effect in the production environment
+ * so there is no configuration of the production environment
+ * For details, please see
+ * https://pro.ant.design/docs/deploy
+ */
+export default {
+ dev: {
+ '/api/': {
+ target: 'https://preview.pro.ant.design',
+ changeOrigin: true,
+ pathRewrite: {
+ '^': '',
+ },
+ },
+ },
+ test: {
+ '/api/': {
+ target: 'https://preview.pro.ant.design',
+ changeOrigin: true,
+ pathRewrite: {
+ '^': '',
+ },
+ },
+ },
+ pre: {
+ '/api/': {
+ target: 'your pre url',
+ changeOrigin: true,
+ pathRewrite: {
+ '^': '',
+ },
+ },
+ },
+};
diff --git a/tsfile-viewer-web-frontend/config/routes.js b/tsfile-viewer-web-frontend/config/routes.js
new file mode 100644
index 0000000..12700c5
--- /dev/null
+++ b/tsfile-viewer-web-frontend/config/routes.js
@@ -0,0 +1,68 @@
+/*
+ * 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.
+ */
+export default [
+ {
+ path: '/user',
+ layout: false,
+ routes: [
+ {
+ path: '/user',
+ routes: [
+ {
+ name: 'login',
+ path: '/user/login/',
+ component: './user/Login',
+ },
+ ],
+ },
+ ],
+ },
+ {
+ path: '/welcome',
+ name: 'welcome',
+ icon: 'smile',
+ component: './Welcome',
+ },
+ {
+ path: '/admin',
+ name: 'admin',
+ icon: 'crown',
+ access: 'canAdmin',
+ component: './Admin',
+ routes: [
+ {
+ path: '/admin/sub-page',
+ name: 'sub-page',
+ icon: 'smile',
+ component: './Welcome',
+ },
+ ],
+ },
+ {
+ name: 'list.table-list',
+ icon: 'table',
+ path: '/list',
+ component: './TableList',
+ },
+ {
+ path: '/',
+ redirect: '/welcome',
+ },
+ {
+ component: './404',
+ },
+];
diff --git a/tsfile-viewer-web-frontend/config/swagger.json b/tsfile-viewer-web-frontend/config/swagger.json
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/tsfile-viewer-web-frontend/config/swagger.json
@@ -0,0 +1 @@
+{}
diff --git a/tsfile-viewer-web-frontend/jest.config.js b/tsfile-viewer-web-frontend/jest.config.js
new file mode 100644
index 0000000..1c30910
--- /dev/null
+++ b/tsfile-viewer-web-frontend/jest.config.js
@@ -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.
+ */
+module.exports = {
+ testURL: 'http://localhost:8000',
+ testEnvironment: './tests/PuppeteerEnvironment',
+ verbose: false,
+ extraSetupFiles: ['./tests/setupTests.js'],
+ globals: {
+ ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION: false,
+ localStorage: null,
+ },
+};
diff --git a/tsfile-viewer-web-frontend/jsconfig.json b/tsfile-viewer-web-frontend/jsconfig.json
new file mode 100644
index 0000000..f87334d
--- /dev/null
+++ b/tsfile-viewer-web-frontend/jsconfig.json
@@ -0,0 +1,10 @@
+{
+ "compilerOptions": {
+ "emitDecoratorMetadata": true,
+ "experimentalDecorators": true,
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["./src/*"]
+ }
+ }
+}
diff --git a/tsfile-viewer-web-frontend/package.json b/tsfile-viewer-web-frontend/package.json
new file mode 100644
index 0000000..98d9fbe
--- /dev/null
+++ b/tsfile-viewer-web-frontend/package.json
@@ -0,0 +1,137 @@
+{
+ "name": "ant-design-pro",
+ "version": "5.0.0",
+ "private": true,
+ "description": "An out-of-box UI solution for enterprise applications",
+ "scripts": {
+ "analyze": "cross-env ANALYZE=1 umi build",
+ "build": "umi build",
+ "deploy": "npm run build && npm run gh-pages",
+ "dev": "npm run start:dev",
+ "gh-pages": "gh-pages -d dist",
+ "i18n-remove": "pro i18n-remove --locale=zh-CN --write",
+ "postinstall": "umi g tmp",
+ "lint": "umi g tmp && npm run lint:js && npm run lint:style && npm run lint:prettier",
+ "lint-staged": "lint-staged",
+ "lint-staged:js": "eslint --ext .js,.jsx,.ts,.tsx ",
+ "lint:fix": "eslint --fix --cache --ext .js,.jsx,.ts,.tsx --format=pretty ./src && npm run lint:style",
+ "lint:js": "eslint --cache --ext .js,.jsx,.ts,.tsx --format=pretty ./src",
+ "lint:prettier": "prettier -c --write \"src/**/*\" --end-of-line auto",
+ "lint:style": "stylelint --fix \"src/**/*.less\" --syntax less",
+ "openapi": "umi openapi",
+ "precommit": "lint-staged",
+ "prettier": "prettier -c --write \"src/**/*\"",
+ "start": "cross-env UMI_ENV=dev umi dev",
+ "start:dev": "cross-env REACT_APP_ENV=dev MOCK=none UMI_ENV=dev umi dev",
+ "start:no-mock": "cross-env MOCK=none UMI_ENV=dev umi dev",
+ "start:no-ui": "cross-env UMI_UI=none UMI_ENV=dev umi dev",
+ "start:pre": "cross-env REACT_APP_ENV=pre UMI_ENV=dev umi dev",
+ "start:test": "cross-env REACT_APP_ENV=test MOCK=none UMI_ENV=dev umi dev",
+ "pretest": "node ./tests/beforeTest",
+ "test": "umi test",
+ "test:all": "node ./tests/run-tests.js",
+ "test:component": "umi test ./src/components",
+ "serve": "umi-serve",
+ "tsc": "tsc --noEmit"
+ },
+ "lint-staged": {
+ "**/*.less": "stylelint --syntax less",
+ "**/*.{js,jsx,ts,tsx}": "npm run lint-staged:js",
+ "**/*.{js,jsx,tsx,ts,less,md,json}": [
+ "prettier --write"
+ ]
+ },
+ "browserslist": [
+ "> 1%",
+ "last 2 versions",
+ "not ie <= 10"
+ ],
+ "dependencies": {
+ "@ant-design/charts": "^0.9.13",
+ "@ant-design/icons": "^4.5.0",
+ "@ant-design/pro-descriptions": "^1.6.8",
+ "@ant-design/pro-form": "^1.18.3",
+ "@ant-design/pro-layout": "^6.15.3",
+ "@ant-design/pro-table": "^2.30.8",
+ "@antv/data-set": "^0.11.0",
+ "@antv/g2plot": "^2.4.20",
+ "@antv/l7": "^2.3.7",
+ "@antv/l7-maps": "^2.3.7",
+ "@antv/l7-react": "^2.1.9",
+ "@types/lodash.debounce": "^4.0.6",
+ "@umijs/route-utils": "^1.0.36",
+ "ahooks": "^2.0.0",
+ "ant-design-pro": "^2.3.2",
+ "ant-virtual-table": "^0.1.11",
+ "antd": "^4.14.0",
+ "bizcharts": "^3.5.3-beta.0",
+ "bizcharts-plugin-slider": "^2.1.1-beta.1",
+ "btoa": "^1.2.1",
+ "codemirror": "^5.65.0",
+ "gg-editor": "^2.0.2",
+ "js-export-excel": "^1.1.4",
+ "js-md5": "^0.7.3",
+ "lodash": "^4.17.11",
+ "lodash-decorators": "^6.0.0",
+ "lodash-es": "^4.17.21",
+ "moment": "^2.25.3",
+ "numeral": "^2.0.6",
+ "nzh": "^1.0.3",
+ "omit.js": "^2.0.2",
+ "react": "^17.0.0",
+ "react-arborist": "^2.1.0",
+ "react-color": "^2.19.3",
+ "react-dev-inspector": "^1.1.1",
+ "react-dom": "^17.0.0",
+ "react-fittext": "^1.0.0",
+ "react-helmet-async": "^1.0.4",
+ "react-router": "^4.3.1",
+ "react-uuid": "^1.0.2",
+ "react-window": "^1.8.6",
+ "sql-formatter": "^4.0.2",
+ "umi": "^3.5.0",
+ "umi-serve": "^1.9.10",
+ "uuidv4": "^6.2.12"
+ },
+ "devDependencies": {
+ "@ant-design/pro-cli": "^2.0.2",
+ "@types/express": "^4.17.0",
+ "@types/history": "^4.7.2",
+ "@types/jest": "^26.0.0",
+ "@types/lodash": "^4.14.144",
+ "@types/react": "^17.0.0",
+ "@types/react-dom": "^17.0.0",
+ "@types/react-helmet": "^6.1.0",
+ "@umijs/fabric": "^2.6.2",
+ "@umijs/openapi": "^1.1.14",
+ "@umijs/plugin-blocks": "^2.0.5",
+ "@umijs/plugin-esbuild": "^1.0.1",
+ "@umijs/plugin-openapi": "^1.2.0",
+ "@umijs/preset-ant-design-pro": "^1.2.0",
+ "@umijs/preset-dumi": "^1.1.7",
+ "@umijs/preset-react": "^1.7.4",
+ "@umijs/yorkie": "^2.0.3",
+ "carlo": "^0.9.46",
+ "cross-env": "^7.0.0",
+ "cross-port-killer": "^1.1.1",
+ "detect-installer": "^1.0.1",
+ "enzyme": "^3.11.0",
+ "eslint": "^7.1.0",
+ "express": "^4.17.1",
+ "gh-pages": "^3.0.0",
+ "jsdom-global": "^3.0.2",
+ "lint-staged": "^10.0.0",
+ "mockjs": "^1.0.1-beta3",
+ "prettier": "^2.3.2",
+ "puppeteer-core": "^8.0.0",
+ "react-codemirror2": "^7.2.1",
+ "stylelint": "^13.0.0",
+ "typescript": "^4.2.2"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "gitHooks": {
+ "commit-msg": "fabric verify-commit"
+ }
+}
diff --git a/tsfile-viewer-web-frontend/pom.xml b/tsfile-viewer-web-frontend/pom.xml
new file mode 100644
index 0000000..34d9b9e
--- /dev/null
+++ b/tsfile-viewer-web-frontend/pom.xml
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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. -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.iotdb</groupId>
+ <artifactId>iotdb-tsfile-viewer</artifactId>
+ <version>0.13.2-SNAPSHOT</version>
+ </parent>
+ <name>frontend</name>
+ <packaging>pom</packaging>
+ <artifactId>iotdb-tsfile-viewer-web-frontend</artifactId>
+
+ <properties>
+ <frontend-project-dist-dir>${project.parent.basedir}/tsfile-viewer-web-frontend/dist</frontend-project-dist-dir>
+ <build.node.version>v16.14.2</build.node.version>
+ <build.yarn.version>v1.22.19</build.yarn.version>
+ </properties>
+
+ <build>
+ <plugins>
+ <plugin>
+ <!-- 插件maven-clean-plugin,用于在编译前,清除之前编译的文件、文件夹等,避免残留之前的内容 -->
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-clean-plugin</artifactId>
+ <version>2.5</version>
+ <configuration>
+ <filesets>
+ <fileset>
+ <!-- front项目打包自动生成的dist目录 -->
+ <directory>${frontend-project-dist-dir}</directory>
+ </fileset>
+ </filesets>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>com.github.eirslett</groupId>
+ <artifactId>frontend-maven-plugin</artifactId>
+ <version>1.12.0</version>
+ <executions>
+ <execution>
+ <id>install node and yarn</id>
+ <goals>
+ <goal>install-node-and-yarn</goal>
+ </goals>
+ <phase>generate-resources</phase>
+ </execution>
+ <execution>
+ <id>yarn install</id>
+ <goals>
+ <goal>yarn</goal>
+ </goals>
+ <phase>generate-resources</phase>
+ <configuration>
+ <arguments>install</arguments>
+ </configuration>
+ </execution>
+ <execution>
+ <id>yarn run build</id>
+ <goals>
+ <goal>yarn</goal>
+ </goals>
+ <phase>compile</phase>
+ <configuration>
+ <arguments>run build</arguments>
+ </configuration>
+ </execution>
+ </executions>
+ <configuration>
+ <nodeVersion>${build.node.version}</nodeVersion>
+ <yarnVersion>${build.yarn.version}</yarnVersion>
+
+ <!-- 若不方便访问官网,可使用国内淘宝镜像-->
+ <nodeDownloadRoot>https://npm.taobao.org/mirrors/node/</nodeDownloadRoot>
+ <yarnDownloadRoot>https://github.com/yarnpkg/yarn/releases/download/</yarnDownloadRoot>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/tsfile-viewer-web-frontend/public/favicon.ico b/tsfile-viewer-web-frontend/public/favicon.ico
new file mode 100644
index 0000000..1977dd1
Binary files /dev/null and b/tsfile-viewer-web-frontend/public/favicon.ico differ
diff --git a/tsfile-viewer-web-frontend/public/icons/icon-128x128.png b/tsfile-viewer-web-frontend/public/icons/icon-128x128.png
new file mode 100644
index 0000000..5e2fb6b
Binary files /dev/null and b/tsfile-viewer-web-frontend/public/icons/icon-128x128.png differ
diff --git a/tsfile-viewer-web-frontend/public/icons/icon-192x192.png b/tsfile-viewer-web-frontend/public/icons/icon-192x192.png
new file mode 100644
index 0000000..5e2fb6b
Binary files /dev/null and b/tsfile-viewer-web-frontend/public/icons/icon-192x192.png differ
diff --git a/tsfile-viewer-web-frontend/public/icons/icon-512x512.png b/tsfile-viewer-web-frontend/public/icons/icon-512x512.png
new file mode 100644
index 0000000..5e2fb6b
Binary files /dev/null and b/tsfile-viewer-web-frontend/public/icons/icon-512x512.png differ
diff --git a/tsfile-viewer-web-frontend/public/iotdb-logo.png b/tsfile-viewer-web-frontend/public/iotdb-logo.png
new file mode 100644
index 0000000..5e2fb6b
Binary files /dev/null and b/tsfile-viewer-web-frontend/public/iotdb-logo.png differ
diff --git a/tsfile-viewer-web-frontend/src/access.js b/tsfile-viewer-web-frontend/src/access.js
new file mode 100644
index 0000000..ac1b172
--- /dev/null
+++ b/tsfile-viewer-web-frontend/src/access.js
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+/**
+ * @see https://umijs.org/zh-CN/plugins/plugin-access
+ * */
+export default function access(initialState) {
+ return {
+ canAdmin: true,
+ };
+}
diff --git a/tsfile-viewer-web-frontend/src/app.jsx b/tsfile-viewer-web-frontend/src/app.jsx
new file mode 100644
index 0000000..8800132
--- /dev/null
+++ b/tsfile-viewer-web-frontend/src/app.jsx
@@ -0,0 +1,105 @@
+/*
+ * 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.
+ */
+import { PageLoading } from '@ant-design/pro-layout';
+import { message } from 'antd';
+import { history,SelectLang } from 'umi';
+import Footer from '@/components/Footer';
+import { forwardRef } from 'react';
+const fowardPath = '/tsfile-tool/v2/';
+/** 获取用户信息比较慢的时候会展示一个 loading */
+
+export const initialStateConfig = {
+ loading: <PageLoading />,
+};
+/**
+ * 异常处理程序
+ 200: '服务器成功返回请求的数据。',
+ 201: '新建或修改数据成功。',
+ 202: '一个请求已经进入后台排队(异步任务)。',
+ 204: '删除数据成功。',
+ 400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。',
+ 401: '用户没有权限(令牌、用户名、密码错误)。',
+ 403: '用户得到授权,但是访问是被禁止的。',
+ 404: '发出的请求针对的是不存在的记录,服务器没有进行操作。',
+ 405: '请求方法不被允许。',
+ 406: '请求的格式不可得。',
+ 410: '请求的资源被永久删除,且不会再得到的。',
+ 422: '当创建一个对象时,发生一个验证错误。',
+ 500: '服务器发生错误,请检查服务器。',
+ 502: '网关错误。',
+ 503: '服务不可用,服务器暂时过载或维护。',
+ 504: '网关超时。',
+ //-----English
+ 200: The server successfully returned the requested data. ',
+ 201: New or modified data is successful. ',
+ 202: A request has entered the background queue (asynchronous task). ',
+ 204: Data deleted successfully. ',
+ 400: 'There was an error in the request sent, and the server did not create or modify data. ',
+ 401: The user does not have permission (token, username, password error). ',
+ 403: The user is authorized, but access is forbidden. ',
+ 404: The request sent was for a record that did not exist. ',
+ 405: The request method is not allowed. ',
+ 406: The requested format is not available. ',
+ 410':
+ 'The requested resource is permanently deleted and will no longer be available. ',
+ 422: When creating an object, a validation error occurred. ',
+ 500: An error occurred on the server, please check the server. ',
+ 502: Gateway error. ',
+ 503: The service is unavailable. ',
+ 504: The gateway timed out. ',
+ * @see https://beta-pro.ant.design/docs/request-cn
+ */
+
+export const request = {
+ errorHandler: (error) => {
+ const { response } = error;
+ if(response.status == '404'){
+ history.push("/exception/404")
+ }
+ if(response.status == '500' || response.status == '502' || response.status == '503' || response.status == '504'){
+ history.push("/exception/500")
+ }
+ throw error;
+ },
+}; // ProLayout 支持的api https://procomponents.ant.design/components/layout
+
+export const layout = ({ initialState }) => {
+ return {
+ rightContentRender: () => <SelectLang />,
+ disableContentMargin: false,
+ footerRender: () => <Footer />,
+ onPageChange: () => {
+ const { location } = history; // 如果没有登录,重定向到 login
+ let pathname = location.pathname.endsWith('/') ?
+ 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){
+ return <span>{route.breadcrumbName}</span>;
+ }
+ return <span>{route.breadcrumbName}</span>;
+ },
+ // 自定义 403 页面
+ // unAccessible: <div>unAccessible</div>,
+ ...initialState?.settings,
+ };
+};
diff --git a/tsfile-viewer-web-frontend/src/components/Footer/index.jsx b/tsfile-viewer-web-frontend/src/components/Footer/index.jsx
new file mode 100644
index 0000000..e555f0b
--- /dev/null
+++ b/tsfile-viewer-web-frontend/src/components/Footer/index.jsx
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+import { useIntl } from 'umi';
+import { GithubOutlined } from '@ant-design/icons';
+import { DefaultFooter } from '@ant-design/pro-layout';
+export default () => {
+ const intl = useIntl();
+ const defaultMessage = intl.formatMessage({
+ id: 'app.copyright.produced',
+ defaultMessage: '蚂蚁集团体验技术部出品',
+ });
+ return (
+ null
+ );
+};
diff --git a/tsfile-viewer-web-frontend/src/components/HeaderDropdown/index.jsx b/tsfile-viewer-web-frontend/src/components/HeaderDropdown/index.jsx
new file mode 100644
index 0000000..3f30795
--- /dev/null
+++ b/tsfile-viewer-web-frontend/src/components/HeaderDropdown/index.jsx
@@ -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.
+ */
+import { Dropdown } from 'antd';
+import React from 'react';
+import classNames from 'classnames';
+import styles from './index.less';
+
+const HeaderDropdown = ({ overlayClassName: cls, ...restProps }) => (
+ <Dropdown overlayClassName={classNames(styles.container, cls)} {...restProps} />
+);
+
+export default HeaderDropdown;
diff --git a/tsfile-viewer-web-frontend/src/components/HeaderDropdown/index.less b/tsfile-viewer-web-frontend/src/components/HeaderDropdown/index.less
new file mode 100644
index 0000000..d773b7a
--- /dev/null
+++ b/tsfile-viewer-web-frontend/src/components/HeaderDropdown/index.less
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+@import '~antd/es/style/themes/default.less';
+
+.container > * {
+ background-color: @popover-bg;
+ border-radius: 4px;
+ box-shadow: @shadow-1-down;
+}
+
+@media screen and (max-width: @screen-xs) {
+ .container {
+ width: 100% !important;
+ }
+ .container > * {
+ border-radius: 0 !important;
+ }
+}
diff --git a/tsfile-viewer-web-frontend/src/components/HeaderSearch/index.jsx b/tsfile-viewer-web-frontend/src/components/HeaderSearch/index.jsx
new file mode 100644
index 0000000..bdd03aa
--- /dev/null
+++ b/tsfile-viewer-web-frontend/src/components/HeaderSearch/index.jsx
@@ -0,0 +1,99 @@
+/*
+ * 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.
+ */
+import { SearchOutlined } from '@ant-design/icons';
+import { AutoComplete, Input } from 'antd';
+import useMergedState from 'rc-util/es/hooks/useMergedState';
+import React, { useRef } from 'react';
+import classNames from 'classnames';
+import styles from './index.less';
+
+const HeaderSearch = (props) => {
+ const {
+ className,
+ defaultValue,
+ onVisibleChange,
+ placeholder,
+ visible,
+ defaultVisible,
+ ...restProps
+ } = props;
+ const inputRef = useRef(null);
+ const [value, setValue] = useMergedState(defaultValue, {
+ value: props.value,
+ onChange: props.onChange,
+ });
+ const [searchMode, setSearchMode] = useMergedState(defaultVisible ?? false, {
+ value: props.visible,
+ onChange: onVisibleChange,
+ });
+ const inputClass = classNames(styles.input, {
+ [styles.show]: searchMode,
+ });
+ return (
+ <div
+ className={classNames(className, styles.headerSearch)}
+ onClick={() => {
+ setSearchMode(true);
+
+ if (searchMode && inputRef.current) {
+ inputRef.current.focus();
+ }
+ }}
+ onTransitionEnd={({ propertyName }) => {
+ if (propertyName === 'width' && !searchMode) {
+ if (onVisibleChange) {
+ onVisibleChange(searchMode);
+ }
+ }
+ }}
+ >
+ <SearchOutlined
+ key="Icon"
+ style={{
+ cursor: 'pointer',
+ }}
+ />
+ <AutoComplete
+ key="AutoComplete"
+ className={inputClass}
+ value={value}
+ options={restProps.options}
+ onChange={setValue}
+ >
+ <Input
+ size="small"
+ ref={inputRef}
+ defaultValue={defaultValue}
+ aria-label={placeholder}
+ placeholder={placeholder}
+ onKeyDown={(e) => {
+ if (e.key === 'Enter') {
+ if (restProps.onSearch) {
+ restProps.onSearch(value);
+ }
+ }
+ }}
+ onBlur={() => {
+ setSearchMode(false);
+ }}
+ />
+ </AutoComplete>
+ </div>
+ );
+};
+
+export default HeaderSearch;
diff --git a/tsfile-viewer-web-frontend/src/components/HeaderSearch/index.less b/tsfile-viewer-web-frontend/src/components/HeaderSearch/index.less
new file mode 100644
index 0000000..0ea845d
--- /dev/null
+++ b/tsfile-viewer-web-frontend/src/components/HeaderSearch/index.less
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+@import '~antd/es/style/themes/default.less';
+
+.headerSearch {
+ display: inline-flex;
+ align-items: center;
+ .input {
+ width: 0;
+ min-width: 0;
+ overflow: hidden;
+ background: transparent;
+ border-radius: 0;
+ transition: width 0.3s, margin-left 0.3s;
+ :global(.ant-select-selection) {
+ background: transparent;
+ }
+ input {
+ box-shadow: none !important;
+ }
+
+ &.show {
+ width: 210px;
+ margin-left: 8px;
+ }
+ }
+}
diff --git a/tsfile-viewer-web-frontend/src/components/NoticeIcon/NoticeIcon.jsx b/tsfile-viewer-web-frontend/src/components/NoticeIcon/NoticeIcon.jsx
new file mode 100644
index 0000000..816613b
--- /dev/null
+++ b/tsfile-viewer-web-frontend/src/components/NoticeIcon/NoticeIcon.jsx
@@ -0,0 +1,130 @@
+/*
+ * 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.
+ */
+import { BellOutlined } from '@ant-design/icons';
+import { Badge, Spin, Tabs } from 'antd';
+import useMergedState from 'rc-util/es/hooks/useMergedState';
+import React from 'react';
+import classNames from 'classnames';
+import NoticeList from './NoticeList';
+import HeaderDropdown from '../HeaderDropdown';
+import styles from './index.less';
+const { TabPane } = Tabs;
+
+const NoticeIcon = (props) => {
+ const getNotificationBox = () => {
+ const {
+ children,
+ loading,
+ onClear,
+ onTabChange,
+ onItemClick,
+ onViewMore,
+ clearText,
+ viewMoreText,
+ } = props;
+
+ if (!children) {
+ return null;
+ }
+
+ const panes = [];
+ React.Children.forEach(children, (child) => {
+ if (!child) {
+ return;
+ }
+
+ const { list, title, count, tabKey, showClear, showViewMore } = child.props;
+ const len = list && list.length ? list.length : 0;
+ const msgCount = count || count === 0 ? count : len;
+ const tabTitle = msgCount > 0 ? `${title} (${msgCount})` : title;
+ panes.push(
+ <TabPane tab={tabTitle} key={tabKey}>
+ <NoticeList
+ clearText={clearText}
+ viewMoreText={viewMoreText}
+ list={list}
+ tabKey={tabKey}
+ onClear={() => onClear && onClear(title, tabKey)}
+ onClick={(item) => onItemClick && onItemClick(item, child.props)}
+ onViewMore={(event) => onViewMore && onViewMore(child.props, event)}
+ showClear={showClear}
+ showViewMore={showViewMore}
+ title={title}
+ />
+ </TabPane>,
+ );
+ });
+ return (
+ <>
+ <Spin spinning={loading} delay={300}>
+ <Tabs className={styles.tabs} onChange={onTabChange}>
+ {panes}
+ </Tabs>
+ </Spin>
+ </>
+ );
+ };
+
+ const { className, count, bell } = props;
+ const [visible, setVisible] = useMergedState(false, {
+ value: props.popupVisible,
+ onChange: props.onPopupVisibleChange,
+ });
+ const noticeButtonClass = classNames(className, styles.noticeButton);
+ const notificationBox = getNotificationBox();
+ const NoticeBellIcon = bell || <BellOutlined className={styles.icon} />;
+ const trigger = (
+ <span
+ className={classNames(noticeButtonClass, {
+ opened: visible,
+ })}
+ >
+ <Badge
+ count={count}
+ style={{
+ boxShadow: 'none',
+ }}
+ className={styles.badge}
+ >
+ {NoticeBellIcon}
+ </Badge>
+ </span>
+ );
+
+ if (!notificationBox) {
+ return trigger;
+ }
+
+ return (
+ <HeaderDropdown
+ placement="bottomRight"
+ overlay={notificationBox}
+ overlayClassName={styles.popover}
+ trigger={['click']}
+ visible={visible}
+ onVisibleChange={setVisible}
+ >
+ {trigger}
+ </HeaderDropdown>
+ );
+};
+
+NoticeIcon.defaultProps = {
+ emptyImage: 'https://gw.alipayobjects.com/zos/rmsportal/wAhyIChODzsoKIOBHcBk.svg',
+};
+NoticeIcon.Tab = NoticeList;
+export default NoticeIcon;
diff --git a/tsfile-viewer-web-frontend/src/components/NoticeIcon/NoticeList.jsx b/tsfile-viewer-web-frontend/src/components/NoticeIcon/NoticeList.jsx
new file mode 100644
index 0000000..967a5ab
--- /dev/null
+++ b/tsfile-viewer-web-frontend/src/components/NoticeIcon/NoticeList.jsx
@@ -0,0 +1,113 @@
+/*
+ * 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.
+ */
+import { Avatar, List } from 'antd';
+import React from 'react';
+import classNames from 'classnames';
+import styles from './NoticeList.less';
+
+const NoticeList = ({
+ list = [],
+ onClick,
+ onClear,
+ title,
+ onViewMore,
+ emptyText,
+ showClear = true,
+ clearText,
+ viewMoreText,
+ showViewMore = false,
+}) => {
+ if (!list || list.length === 0) {
+ return (
+ <div className={styles.notFound}>
+ <img
+ src="https://gw.alipayobjects.com/zos/rmsportal/sAuJeJzSKbUmHfBQRzmZ.svg"
+ alt="not found"
+ />
+ <div>{emptyText}</div>
+ </div>
+ );
+ }
+
+ return (
+ <div>
+ <List
+ className={styles.list}
+ dataSource={list}
+ renderItem={(item, i) => {
+ const itemCls = classNames(styles.item, {
+ [styles.read]: item.read,
+ }); // eslint-disable-next-line no-nested-ternary
+
+ const leftIcon = item.avatar ? (
+ typeof item.avatar === 'string' ? (
+ <Avatar className={styles.avatar} src={item.avatar} />
+ ) : (
+ <span className={styles.iconElement}>{item.avatar}</span>
+ )
+ ) : null;
+ return (
+ <List.Item
+ className={itemCls}
+ key={item.key || i}
+ onClick={() => {
+ onClick?.(item);
+ }}
+ >
+ <List.Item.Meta
+ className={styles.meta}
+ avatar={leftIcon}
+ title={
+ <div className={styles.title}>
+ {item.title}
+ <div className={styles.extra}>{item.extra}</div>
+ </div>
+ }
+ description={
+ <div>
+ <div className={styles.description}>{item.description}</div>
+ <div className={styles.datetime}>{item.datetime}</div>
+ </div>
+ }
+ />
+ </List.Item>
+ );
+ }}
+ />
+ <div className={styles.bottomBar}>
+ {showClear ? (
+ <div onClick={onClear}>
+ {clearText} {title}
+ </div>
+ ) : null}
+ {showViewMore ? (
+ <div
+ onClick={(e) => {
+ if (onViewMore) {
+ onViewMore(e);
+ }
+ }}
+ >
+ {viewMoreText}
+ </div>
+ ) : null}
+ </div>
+ </div>
+ );
+};
+
+export default NoticeList;
diff --git a/tsfile-viewer-web-frontend/src/components/NoticeIcon/NoticeList.less b/tsfile-viewer-web-frontend/src/components/NoticeIcon/NoticeList.less
new file mode 100644
index 0000000..424f59d
--- /dev/null
+++ b/tsfile-viewer-web-frontend/src/components/NoticeIcon/NoticeList.less
@@ -0,0 +1,119 @@
+/*
+ * 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.
+ */
+@import '~antd/es/style/themes/default.less';
+
+.list {
+ max-height: 400px;
+ overflow: auto;
+ &::-webkit-scrollbar {
+ display: none;
+ }
+ .item {
+ padding-right: 24px;
+ padding-left: 24px;
+ overflow: hidden;
+ cursor: pointer;
+ transition: all 0.3s;
+
+ .meta {
+ width: 100%;
+ }
+
+ .avatar {
+ margin-top: 4px;
+ background: @component-background;
+ }
+ .iconElement {
+ font-size: 32px;
+ }
+
+ &.read {
+ opacity: 0.4;
+ }
+ &:last-child {
+ border-bottom: 0;
+ }
+ &:hover {
+ background: @primary-1;
+ }
+ .title {
+ margin-bottom: 8px;
+ font-weight: normal;
+ }
+ .description {
+ font-size: 12px;
+ line-height: @line-height-base;
+ }
+ .datetime {
+ margin-top: 4px;
+ font-size: 12px;
+ line-height: @line-height-base;
+ }
+ .extra {
+ float: right;
+ margin-top: -1.5px;
+ margin-right: 0;
+ color: @text-color-secondary;
+ font-weight: normal;
+ }
+ }
+ .loadMore {
+ padding: 8px 0;
+ color: @primary-6;
+ text-align: center;
+ cursor: pointer;
+ &.loadedAll {
+ color: rgba(0, 0, 0, 0.25);
+ cursor: unset;
+ }
+ }
+}
+
+.notFound {
+ padding: 73px 0 88px;
+ color: @text-color-secondary;
+ text-align: center;
+ img {
+ display: inline-block;
+ height: 76px;
+ margin-bottom: 16px;
+ }
+}
+
+.bottomBar {
+ height: 46px;
+ color: @text-color;
+ line-height: 46px;
+ text-align: center;
+ border-top: 1px solid @border-color-split;
+ border-radius: 0 0 @border-radius-base @border-radius-base;
+ transition: all 0.3s;
+ div {
+ display: inline-block;
+ width: 50%;
+ cursor: pointer;
+ transition: all 0.3s;
+ user-select: none;
+
+ &:only-child {
+ width: 100%;
+ }
+ &:not(:only-child):last-child {
+ border-left: 1px solid @border-color-split;
+ }
+ }
+}
diff --git a/tsfile-viewer-web-frontend/src/components/NoticeIcon/index.jsx b/tsfile-viewer-web-frontend/src/components/NoticeIcon/index.jsx
new file mode 100644
index 0000000..e15f912
--- /dev/null
+++ b/tsfile-viewer-web-frontend/src/components/NoticeIcon/index.jsx
@@ -0,0 +1,163 @@
+/*
+ * 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.
+ */
+import { useEffect, useState } from 'react';
+import { Tag, message } from 'antd';
+import { groupBy } from 'lodash';
+import moment from 'moment';
+import { useModel } from 'umi';
+import { getNotices } from '@/services/ant-design-pro/api';
+import NoticeIcon from './NoticeIcon';
+import styles from './index.less';
+
+const getNoticeData = (notices) => {
+ if (!notices || notices.length === 0 || !Array.isArray(notices)) {
+ return {};
+ }
+
+ const newNotices = notices.map((notice) => {
+ const newNotice = { ...notice };
+
+ if (newNotice.datetime) {
+ newNotice.datetime = moment(notice.datetime).fromNow();
+ }
+
+ if (newNotice.id) {
+ newNotice.key = newNotice.id;
+ }
+
+ if (newNotice.extra && newNotice.status) {
+ const color = {
+ todo: '',
+ processing: 'blue',
+ urgent: 'red',
+ doing: 'gold',
+ }[newNotice.status];
+ newNotice.extra = (
+ <Tag
+ color={color}
+ style={{
+ marginRight: 0,
+ }}
+ >
+ {newNotice.extra}
+ </Tag>
+ );
+ }
+
+ return newNotice;
+ });
+ return groupBy(newNotices, 'type');
+};
+
+const getUnreadData = (noticeData) => {
+ const unreadMsg = {};
+ Object.keys(noticeData).forEach((key) => {
+ const value = noticeData[key];
+
+ if (!unreadMsg[key]) {
+ unreadMsg[key] = 0;
+ }
+
+ if (Array.isArray(value)) {
+ unreadMsg[key] = value.filter((item) => !item.read).length;
+ }
+ });
+ return unreadMsg;
+};
+
+const NoticeIconView = () => {
+ const { initialState } = useModel('@@initialState');
+ const { currentUser } = initialState || {};
+ const [notices, setNotices] = useState([]);
+ useEffect(() => {
+ getNotices().then(({ data }) => setNotices(data || []));
+ }, []);
+ const noticeData = getNoticeData(notices);
+ const unreadMsg = getUnreadData(noticeData || {});
+
+ const changeReadState = (id) => {
+ setNotices(
+ notices.map((item) => {
+ const notice = { ...item };
+
+ if (notice.id === id) {
+ notice.read = true;
+ }
+
+ return notice;
+ }),
+ );
+ };
+
+ const clearReadState = (title, key) => {
+ setNotices(
+ notices.map((item) => {
+ const notice = { ...item };
+
+ if (notice.type === key) {
+ notice.read = true;
+ }
+
+ return notice;
+ }),
+ );
+ message.success(`${'清空了'} ${title}`);
+ };
+
+ return (
+ <NoticeIcon
+ className={styles.action}
+ count={currentUser && currentUser.unreadCount}
+ onItemClick={(item) => {
+ changeReadState(item.id);
+ }}
+ onClear={(title, key) => clearReadState(title, key)}
+ loading={false}
+ clearText="清空"
+ viewMoreText="查看更多"
+ onViewMore={() => message.info('Click on view more')}
+ clearClose
+ >
+ <NoticeIcon.Tab
+ tabKey="notification"
+ count={unreadMsg.notification}
+ list={noticeData.notification}
+ title="通知"
+ emptyText="你已查看所有通知"
+ showViewMore
+ />
+ <NoticeIcon.Tab
+ tabKey="message"
+ count={unreadMsg.message}
+ list={noticeData.message}
+ title="消息"
+ emptyText="您已读完所有消息"
+ showViewMore
+ />
+ <NoticeIcon.Tab
+ tabKey="event"
+ title="待办"
+ emptyText="你已完成所有待办"
+ count={unreadMsg.event}
+ list={noticeData.event}
+ showViewMore
+ />
+ </NoticeIcon>
+ );
+};
+
+export default NoticeIconView;
diff --git a/tsfile-viewer-web-frontend/src/components/NoticeIcon/index.less b/tsfile-viewer-web-frontend/src/components/NoticeIcon/index.less
new file mode 100644
index 0000000..39c7848
--- /dev/null
+++ b/tsfile-viewer-web-frontend/src/components/NoticeIcon/index.less
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+@import '~antd/es/style/themes/default.less';
+
+.popover {
+ position: relative;
+ width: 336px;
+}
+
+.noticeButton {
+ display: inline-block;
+ cursor: pointer;
+ transition: all 0.3s;
+}
+.icon {
+ padding: 4px;
+ vertical-align: middle;
+}
+
+.badge {
+ font-size: 16px;
+}
+
+.tabs {
+ :global {
+ .ant-tabs-nav-list {
+ margin: auto;
+ }
+
+ .ant-tabs-nav-scroll {
+ text-align: center;
+ }
+ .ant-tabs-bar {
+ margin-bottom: 0;
+ }
+ }
+}
diff --git a/tsfile-viewer-web-frontend/src/e2e/baseLayout.e2e.js b/tsfile-viewer-web-frontend/src/e2e/baseLayout.e2e.js
new file mode 100644
index 0000000..5373865
--- /dev/null
+++ b/tsfile-viewer-web-frontend/src/e2e/baseLayout.e2e.js
@@ -0,0 +1,77 @@
+/*
+ * 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.
+ */
+const { uniq } = require('lodash');
+const RouterConfig = require('../../config/config').default.routes;
+
+const BASE_URL = `http://localhost:${process.env.PORT || 8001}`;
+
+function formatter(routes, parentPath = '') {
+ const fixedParentPath = parentPath.replace(/\/{1,}/g, '/');
+ let result = [];
+ routes.forEach((item) => {
+ if (item.path && !item.path.startsWith('/')) {
+ result.push(`${fixedParentPath}/${item.path}`.replace(/\/{1,}/g, '/'));
+ }
+ if (item.path && item.path.startsWith('/')) {
+ result.push(`${item.path}`.replace(/\/{1,}/g, '/'));
+ }
+ if (item.routes) {
+ result = result.concat(
+ formatter(item.routes, item.path ? `${fixedParentPath}/${item.path}` : parentPath),
+ );
+ }
+ });
+ return uniq(result.filter((item) => !!item));
+}
+
+beforeEach(async () => {
+ await page.goto(`${BASE_URL}`);
+ await page.evaluate(() => {
+ localStorage.setItem('antd-pro-authority', '["admin"]');
+ });
+});
+
+describe('Ant Design Pro E2E test', () => {
+ const testPage = (path) => async () => {
+ await page.goto(`${BASE_URL}${path}`);
+ await page.waitForSelector('footer', {
+ timeout: 2000,
+ });
+ const haveFooter = await page.evaluate(
+ () => document.getElementsByTagName('footer').length > 0,
+ );
+ expect(haveFooter).toBeTruthy();
+ };
+
+ const routers = formatter(RouterConfig);
+ routers.forEach((route) => {
+ it(`test pages ${route}`, testPage(route));
+ });
+
+ it('topmenu should have footer', async () => {
+ const params = '?navTheme=light&layout=topmenu';
+ await page.goto(`${BASE_URL}${params}`);
+ await page.waitForSelector('footer', {
+ timeout: 2000,
+ });
+
+ const haveFooter = await page.evaluate(
+ () => document.getElementsByTagName('footer').length > 0,
+ );
+ expect(haveFooter).toBeTruthy();
+ });
+});
diff --git a/tsfile-viewer-web-frontend/src/global.less b/tsfile-viewer-web-frontend/src/global.less
new file mode 100644
index 0000000..ac26617
--- /dev/null
+++ b/tsfile-viewer-web-frontend/src/global.less
@@ -0,0 +1,73 @@
+/*
+ * 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.
+ */
+@import '~antd/es/style/themes/default.less';
+
+html,
+body,
+#root {
+ height: 100%;
+}
+
+.colorWeak {
+ filter: invert(80%);
+}
+
+.ant-layout {
+ min-height: 100vh;
+}
+.ant-pro-sider.ant-layout-sider.ant-pro-sider-fixed {
+ left: unset;
+}
+
+canvas {
+ display: block;
+}
+
+body {
+ text-rendering: optimizeLegibility;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+ul,
+ol {
+ list-style: none;
+}
+
+@media (max-width: @screen-xs) {
+ .ant-table {
+ width: 100%;
+ overflow-x: auto;
+ &-thead > tr,
+ &-tbody > tr {
+ > th,
+ > td {
+ white-space: pre;
+ > span {
+ display: block;
+ }
+ }
+ }
+ }
+}
+
+// Compatible with IE11
+@media screen and(-ms-high-contrast: active), (-ms-high-contrast: none) {
+ body .ant-design-pro > .ant-layout {
+ min-height: 100vh;
+ }
+}
diff --git a/tsfile-viewer-web-frontend/src/locales/en-US.js b/tsfile-viewer-web-frontend/src/locales/en-US.js
new file mode 100644
index 0000000..ff117bd
--- /dev/null
+++ b/tsfile-viewer-web-frontend/src/locales/en-US.js
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+import menu from './en-US/menu';
+import pwa from './en-US/pwa';
+import tsviewer from './en-US/tsviewer';
+export default {
+ 'navBar.lang': 'Languages',
+ 'layout.user.link.help': 'Help',
+ 'layout.user.link.privacy': 'Privacy',
+ 'layout.user.link.terms': 'Terms',
+ 'app.copyright.produced': 'Produced by Ant Financial Experience Department',
+ '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',
+ ...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
new file mode 100644
index 0000000..c7a9093
--- /dev/null
+++ b/tsfile-viewer-web-frontend/src/locales/en-US/menu.js
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+export default {
+ 'menu.platform.file-preview': 'File Preview',
+ 'menu.exception.403': '403',
+ 'menu.exception.404': '404',
+ 'menu.exception.500': '500',
+ 'menu.exception': 'Exception',
+};
diff --git a/tsfile-viewer-web-frontend/src/locales/en-US/pwa.js b/tsfile-viewer-web-frontend/src/locales/en-US/pwa.js
new file mode 100644
index 0000000..b1421a3
--- /dev/null
+++ b/tsfile-viewer-web-frontend/src/locales/en-US/pwa.js
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+export default {
+ 'app.pwa.offline': 'You are offline now',
+ 'app.pwa.serviceworker.updated': 'New content is available',
+ 'app.pwa.serviceworker.updated.hint': 'Please press the "Refresh" button to reload current page',
+ 'app.pwa.serviceworker.updated.ok': 'Refresh',
+};
diff --git a/tsfile-viewer-web-frontend/src/locales/en-US/tsviewer.js b/tsfile-viewer-web-frontend/src/locales/en-US/tsviewer.js
new file mode 100644
index 0000000..28ddaa7
--- /dev/null
+++ b/tsfile-viewer-web-frontend/src/locales/en-US/tsviewer.js
@@ -0,0 +1,62 @@
+/*
+ * 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.
+ */
+export default {
+ 'tsviewer.fileSelect.file': 'File',
+ 'tsviewer.fileSelect.status': 'Status',
+ 'tsviewer.fileSelect.unload': 'Unload',
+ 'tsviewer.fileSelect.loaded': 'Loaded',
+ 'tsviewer.fileSelect.operation': 'Operation',
+ 'tsviewer.fileSelect.accessDirectory': 'Access',
+ 'tsviewer.fileSelect.openFile': 'OpenFile',
+ 'tsviewer.fileSelect.loadFile': 'LoadFile',
+ 'tsviewer.fileSelect.unloadFile': 'UnloadFile',
+ 'tsviewer.fileSelect.fileUnloaded': 'FileUnloaded',
+ 'tsviewer.fileSelect.fileManagement': 'FileManagement',
+ 'tsviewer.fileSelect.path': 'Path',
+ 'tsviewer.fileSelect.openPath': 'OpenPath',
+ 'tsviewer.fileSelect.fileName': 'FileName',
+
+ 'tsviewer.more.briefInfo': 'BriefInfo',
+ 'tsviewer.more.structureMap': 'StructureMap',
+ 'tsviewer.more.beginDate': 'BeginDate',
+ 'tsviewer.more.endDate': 'EndDate',
+ 'tsviewer.more.notNull': 'Not Null',
+
+ 'tsviewer.overview': 'Overview',
+ 'tsviewer.search': 'Search',
+ 'tsviewer.opened': 'opended',
+ 'tsviewer.index.explanation': '1. click the FileManagement button,select a file',
+ 'tsviewer.index.explanation1': '2. Overview tab, you can view the structure and data of the tsfile file',
+ 'tsviewer.index.explanation2': '3. Search tab, you can query all the data of a measurement',
+ 'tsviewer.fileManagement.explanation': '1. Select the tsfile to open, this page only displays files ending with .tsfile and folders',
+ 'tsviewer.fileManagement.explanation1': '2. The breadcrumbs display the corresponding file path, you can click on the breadcrumbs to jump to the corresponding path',
+
+ 'tsviewer.popDetail.chunkgroup.explanation': '1. On the chunkGroup display page, the left side is the chunkgroup list. The content in the list, when the time series is aligned at that time, is deviceId, and when the time series is not aligned, it is deviceId+measurementId',
+ 'tsviewer.popDetail.chunkgroup.explanation1': '2. Click on the left side of the chunkgroup and the right side displays the detailed information of the current chunkgroup; click on the structure map on the right side to view more detailed content of the current chunkgroup',
+
+ 'tsviewer.popDetail.timeseriesIndex.explanation': '1. The timeseriesIndex display page, the left side is the timeseriesIndex list, the content in the list, when the time series is aligned at that time, it is deviceId, and when the time series is not aligned, it is deviceId+measurementId',
+ 'tsviewer.popDetail.timeseriesIndex.explanation1': '2. Click on the left side of timeseriesIndex and the right side displays the detailed information of the current timeseriesIndex; click on the structure map on the right side to view more detailed content of the current timeseriesIndex',
+
+ 'tsviewer.popDetail.indexOfTimeseriesIndex.explanation': '1. IndexOfTimeseriesIndex display page, the following tree structure represents the IndexOfTimeseriesIndex index; the most leaf node represents the index to timeseriesIndex; click the leaf node to open its corresponding chunkGroup information',
+
+ 'tsviewer.moreChunkGroup.chunk.explanation': '1. Display the corresponding chunk information in a tree structure. The first tree represents the chunk. Click the chunk to display the page information under the chunk. Click the page information; the corresponding pageData will pop up',
+ 'tsviewer.moreChunkGroup.chunk.explanation1': '2. Example: 1. targetHost[8], 1. is the serial number, which is convenient for viewing; before [] represents offset, that is, the position in tsfile',
+ 'tsviewer.moreChunkGroup.chunk.explanation2': '3. The chunk displayed by the aligned time series is a logical aggregation chunk, and its corresponding structure is timeseriesChunk and valueChunk',
+
+ 'tsviewer.moreChunkGroup.pageData.explanation': '1. Display pageData in table form, add serial number column for easy viewing; date format can be switched in timestamp',
+
+}
\ No newline at end of file
diff --git a/tsfile-viewer-web-frontend/src/locales/zh-CN.js b/tsfile-viewer-web-frontend/src/locales/zh-CN.js
new file mode 100644
index 0000000..3c18f01
--- /dev/null
+++ b/tsfile-viewer-web-frontend/src/locales/zh-CN.js
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+import menu from './zh-CN/menu';
+import pwa from './zh-CN/pwa';
+import tsviewer from './zh-CN/tsviewer';
+export default {
+ 'navBar.lang': '语言',
+ 'layout.user.link.help': '帮助',
+ 'layout.user.link.privacy': '隐私',
+ 'layout.user.link.terms': '条款',
+ 'app.copyright.produced': '蚂蚁集团体验技术部出品',
+ 'app.preview.down.block': '下载此页面到本地项目',
+ 'app.welcome.link.fetch-blocks': '获取全部区块',
+ 'app.welcome.link.block-list': '基于 block 开发,快速构建标准页面',
+ ...menu,
+ ...pwa,
+ ...tsviewer,
+};
diff --git a/tsfile-viewer-web-frontend/src/locales/zh-CN/menu.js b/tsfile-viewer-web-frontend/src/locales/zh-CN/menu.js
new file mode 100644
index 0000000..d019c19
--- /dev/null
+++ b/tsfile-viewer-web-frontend/src/locales/zh-CN/menu.js
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+export default {
+ 'menu.tsfile-tool': 'Tsfile查看器',
+ 'menu.exception.403': '403',
+ 'menu.exception.404': '404',
+ 'menu.exception.500': '500',
+ 'menu.exception': 'Exception',
+};
diff --git a/tsfile-viewer-web-frontend/src/locales/zh-CN/pwa.js b/tsfile-viewer-web-frontend/src/locales/zh-CN/pwa.js
new file mode 100644
index 0000000..18633c1
--- /dev/null
+++ b/tsfile-viewer-web-frontend/src/locales/zh-CN/pwa.js
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+export default {
+ 'app.pwa.offline': '当前处于离线状态',
+ 'app.pwa.serviceworker.updated': '有新内容',
+ 'app.pwa.serviceworker.updated.hint': '请点击“刷新”按钮或者手动刷新页面',
+ 'app.pwa.serviceworker.updated.ok': '刷新',
+};
diff --git a/tsfile-viewer-web-frontend/src/locales/zh-CN/tsviewer.js b/tsfile-viewer-web-frontend/src/locales/zh-CN/tsviewer.js
new file mode 100644
index 0000000..4728b5d
--- /dev/null
+++ b/tsfile-viewer-web-frontend/src/locales/zh-CN/tsviewer.js
@@ -0,0 +1,61 @@
+/*
+ * 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.
+ */
+export default {
+ 'tsviewer.fileSelect.file': '文件',
+ 'tsviewer.fileSelect.status': '状态',
+ 'tsviewer.fileSelect.unload': '未加载',
+ 'tsviewer.fileSelect.loaded': '已加载',
+ 'tsviewer.fileSelect.operation': '操作',
+ 'tsviewer.fileSelect.accessDirectory': '打开文件夹',
+ 'tsviewer.fileSelect.openFile': '打开文件',
+ 'tsviewer.fileSelect.loadFile': '加载文件',
+ 'tsviewer.fileSelect.unloadFile': '卸载文件',
+ 'tsviewer.fileSelect.fileUnloaded': '文件已经卸载',
+ 'tsviewer.fileSelect.fileManagement': '文件管理',
+ 'tsviewer.fileSelect.path': '路径',
+ 'tsviewer.fileSelect.openPath': '打开路径',
+ 'tsviewer.fileSelect.fileName': '文件名称',
+
+ 'tsviewer.more.briefInfo': '简略信息',
+ 'tsviewer.more.structureMap': '结构图谱',
+ 'tsviewer.more.beginDate': '开启日期',
+ 'tsviewer.more.endDate': '结束日期',
+ 'tsviewer.more.notNull': '不能为空',
+
+ 'tsviewer.overview': '总览',
+ 'tsviewer.search': '数据查询',
+ 'tsviewer.opened': '已打开',
+ 'tsviewer.index.explanation': '1、点击文件管理,选择一个文件',
+ 'tsviewer.index.explanation1': '2、总览页签,可以查看tsfile文件的结构以及数据',
+ 'tsviewer.index.explanation2': '3、数据查询页签,可以查询某个measurement的所有数据',
+ 'tsviewer.fileManagement.explanation': '1、选择要打开的tsfile,本页面只展示文件夹和以.tsfile结尾的文件',
+ 'tsviewer.fileManagement.explanation1': '2、面包屑展示对应的文件路径,你可以点击面包屑来跳转到对应的路径里',
+
+ 'tsviewer.popDetail.chunkgroup.explanation': '1、chunkGroup展示页面,左侧为chunkgroup列表,列表中的内容,当时对齐时间序列时,为deviceId,非对齐时间序列则是deviceId+measurementId',
+ 'tsviewer.popDetail.chunkgroup.explanation1': '2、点击左侧chunkgroup右侧展示当前chunkgroup的详情信息;点击右侧的结构图谱,可以查看当前chunkgroup更加详细的内容',
+
+ 'tsviewer.popDetail.timeseriesIndex.explanation': '1、timeseriesIndex展示页面,左侧为timeseriesIndex列表,列表中的内容,当时对齐时间序列时,为deviceId,非对齐时间序列则是deviceId+measurementId',
+ 'tsviewer.popDetail.timeseriesIndex.explanation1': '2、点击左侧timeseriesIndex右侧展示当前timeseriesIndex的详情信息;点击右侧的结构图谱,可以查看当前timeseriesIndex更加详细的内容',
+
+ 'tsviewer.popDetail.indexOfTimeseriesIndex.explanation': '1、IndexOfTimeseriesIndex展示页面,以下的树结构表示IndexOfTimeseriesIndex索引;最叶子节点表示索引到timeseriesIndex;点击叶子结点,打开其对应的chunkGroup信息',
+
+ 'tsviewer.moreChunkGroup.chunk.explanation': '1、以树状结构展示对应的chunk信息,第一层树为代表chunk,点击chunk,显示chunk下的page信息,点击page信息;会弹出对应的pageData',
+ 'tsviewer.moreChunkGroup.chunk.explanation1': '2、例子:1. targetHost[8], 1. 是序号,方便查看;[]之前代表的是offset,即tsfile中的位置',
+ 'tsviewer.moreChunkGroup.chunk.explanation2': '3、对齐时间序列展示的chunk为逻辑聚合chunk,其对应的结构为timeseriesChunk和valueChunk',
+
+ 'tsviewer.moreChunkGroup.pageData.explanation': '1、以table表格展示pageData,添加序号列,方便查看;timestamp中可以切换日期格式',
+}
\ No newline at end of file
diff --git a/tsfile-viewer-web-frontend/src/manifest.json b/tsfile-viewer-web-frontend/src/manifest.json
new file mode 100644
index 0000000..839bc5b
--- /dev/null
+++ b/tsfile-viewer-web-frontend/src/manifest.json
@@ -0,0 +1,22 @@
+{
+ "name": "Ant Design Pro",
+ "short_name": "Ant Design Pro",
+ "display": "standalone",
+ "start_url": "./?utm_source=homescreen",
+ "theme_color": "#002140",
+ "background_color": "#001529",
+ "icons": [
+ {
+ "src": "icons/icon-192x192.png",
+ "sizes": "192x192"
+ },
+ {
+ "src": "icons/icon-128x128.png",
+ "sizes": "128x128"
+ },
+ {
+ "src": "icons/icon-512x512.png",
+ "sizes": "512x512"
+ }
+ ]
+}
diff --git a/tsfile-viewer-web-frontend/src/pages/404.jsx b/tsfile-viewer-web-frontend/src/pages/404.jsx
new file mode 100644
index 0000000..438cd2a
--- /dev/null
+++ b/tsfile-viewer-web-frontend/src/pages/404.jsx
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+import { Button, Result } from 'antd';
+import React from 'react';
+import { history } from 'umi';
+
+const NoFoundPage = () => (
+ <Result
+ status="404"
+ title="404"
+ subTitle="Sorry, the page you visited does not exist."
+ extra={
+ <Button type="primary" onClick={() => history.push('/')}>
+ Back Home
+ </Button>
+ }
+ />
+);
+
+export default NoFoundPage;
diff --git a/tsfile-viewer-web-frontend/src/pages/Admin.jsx b/tsfile-viewer-web-frontend/src/pages/Admin.jsx
new file mode 100644
index 0000000..07b8358
--- /dev/null
+++ b/tsfile-viewer-web-frontend/src/pages/Admin.jsx
@@ -0,0 +1,68 @@
+/*
+ * 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.
+ */
+import React from 'react';
+import { HeartTwoTone, SmileTwoTone } from '@ant-design/icons';
+import { Card, Typography, Alert } from 'antd';
+import { PageHeaderWrapper } from '@ant-design/pro-layout';
+import { useIntl } from 'umi';
+export default () => {
+ const intl = useIntl();
+ return (
+ <PageHeaderWrapper
+ content={intl.formatMessage({
+ id: 'pages.admin.subPage.title',
+ defaultMessage: 'This page can only be viewed by admin',
+ })}
+ >
+ <Card>
+ <Alert
+ message={intl.formatMessage({
+ id: 'pages.welcome.alertMessage',
+ defaultMessage: 'Faster and stronger heavy-duty components have been released.',
+ })}
+ type="success"
+ showIcon
+ banner
+ style={{
+ margin: -12,
+ marginBottom: 48,
+ }}
+ />
+ <Typography.Title
+ level={2}
+ style={{
+ textAlign: 'center',
+ }}
+ >
+ <SmileTwoTone /> Ant Design Pro <HeartTwoTone twoToneColor="#eb2f96" /> You
+ </Typography.Title>
+ </Card>
+ <p
+ style={{
+ textAlign: 'center',
+ marginTop: 24,
+ }}
+ >
+ Want to add more pages? Please refer to{' '}
+ <a href="https://pro.ant.design/docs/block-cn" target="_blank" rel="noopener noreferrer">
+ use block
+ </a>
+ 。
+ </p>
+ </PageHeaderWrapper>
+ );
+};
diff --git a/tsfile-viewer-web-frontend/src/pages/Welcome.jsx b/tsfile-viewer-web-frontend/src/pages/Welcome.jsx
new file mode 100644
index 0000000..5cfad7c
--- /dev/null
+++ b/tsfile-viewer-web-frontend/src/pages/Welcome.jsx
@@ -0,0 +1,79 @@
+/*
+ * 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.
+ */
+import React from 'react';
+import { PageContainer } from '@ant-design/pro-layout';
+import { Card, Alert, Typography } from 'antd';
+import { useIntl, FormattedMessage } from 'umi';
+import styles from './Welcome.less';
+
+const CodePreview = ({ children }) => (
+ <pre className={styles.pre}>
+ <code>
+ <Typography.Text copyable>{children}</Typography.Text>
+ </code>
+ </pre>
+);
+
+export default () => {
+ const intl = useIntl();
+ return (
+ <PageContainer>
+ <Card>
+ <Alert
+ message={intl.formatMessage({
+ id: 'pages.welcome.alertMessage',
+ defaultMessage: 'Faster and stronger heavy-duty components have been released.',
+ })}
+ type="success"
+ showIcon
+ banner
+ style={{
+ margin: -12,
+ marginBottom: 24,
+ }}
+ />
+ <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>
+ </Typography.Text>
+ <CodePreview>yarn add @ant-design/pro-table</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>
+ </Typography.Text>
+ <CodePreview>yarn add @ant-design/pro-layout</CodePreview>
+ </Card>
+ </PageContainer>
+ );
+};
diff --git a/tsfile-viewer-web-frontend/src/pages/Welcome.less b/tsfile-viewer-web-frontend/src/pages/Welcome.less
new file mode 100644
index 0000000..71b47cb
--- /dev/null
+++ b/tsfile-viewer-web-frontend/src/pages/Welcome.less
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+@import '~antd/lib/style/themes/default.less';
+
+.pre {
+ margin: 12px 0;
+ padding: 12px 20px;
+ background: @input-bg;
+ box-shadow: @card-shadow;
+}
diff --git a/tsfile-viewer-web-frontend/src/pages/exception/403/index.jsx b/tsfile-viewer-web-frontend/src/pages/exception/403/index.jsx
new file mode 100644
index 0000000..083b796
--- /dev/null
+++ b/tsfile-viewer-web-frontend/src/pages/exception/403/index.jsx
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+import { Link } from 'umi';
+import { Result, Button } from 'antd';
+export default () => (
+ <Result
+ status="403"
+ title="403"
+ style={{
+ background: 'none',
+ }}
+ subTitle="Sorry, you don't have access to this page."
+ extra={
+ <Link to="/">
+ <Button type="primary">Back to home</Button>
+ </Link>
+ }
+ />
+);
diff --git a/tsfile-viewer-web-frontend/src/pages/exception/404/index.jsx b/tsfile-viewer-web-frontend/src/pages/exception/404/index.jsx
new file mode 100644
index 0000000..359efc3
--- /dev/null
+++ b/tsfile-viewer-web-frontend/src/pages/exception/404/index.jsx
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+import { Link } from 'umi';
+import { Result, Button } from 'antd';
+export default () => (
+ <Result
+ status="404"
+ title="404"
+ style={{
+ background: 'none',
+ }}
+ subTitle="Sorry, the page you visited does not exist."
+ extra={
+ <Link to="/">
+ <Button type="primary">Back Home</Button>
+ </Link>
+ }
+ />
+);
diff --git a/tsfile-viewer-web-frontend/src/pages/exception/500/index.jsx b/tsfile-viewer-web-frontend/src/pages/exception/500/index.jsx
new file mode 100644
index 0000000..deb8082
--- /dev/null
+++ b/tsfile-viewer-web-frontend/src/pages/exception/500/index.jsx
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+import { Link } from 'umi';
+import { Result, Button } from 'antd';
+export default () => (
+ <Result
+ status="500"
+ title="500"
+ style={{
+ background: 'none',
+ }}
+ subTitle="Sorry, the server is reporting an error."
+ extra={
+ <Link to="/">
+ <Button type="primary">Back Home</Button>
+ </Link>
+ }
+ />
+);
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
new file mode 100644
index 0000000..18f5eb1
--- /dev/null
+++ b/tsfile-viewer-web-frontend/src/pages/tsfile-tool/v2/fileSelect/fileSelect.jsx
@@ -0,0 +1,364 @@
+/*
+ * 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.
+ */
+import { Button, Modal, Input, Col, Row, Breadcrumb, notification } from 'antd';
+import React, { useState, useEffect } from 'react';
+import { Space, Table, Tooltip, Progress } from 'antd';
+import { FolderOpenTwoTone, FileTwoTone, QuestionCircleOutlined } from '@ant-design/icons';
+import {
+ loadFileUsingPOST, getProcessUsingPOST, showFileListUsingPOST, unLoadFileUsingPOST,
+ getBaseinfoUsingPOST, showLoadedFileListUsingPOST
+} from '@/services/swagger1/tsfileViewerController'
+import { useIntl } from 'umi';
+
+const FileSelect = (props) => {
+ const [visible, setVisible] = useState(false);
+ const [breadData, setBreadData] = useState();
+ const [directoryPath, setDirectoryPath] = useState('')
+ const [tableData, setTableData] = useState()
+ const [breadArray, setBreadArray] = useState([])
+ const [progressTitle, setProgressTitle] = useState()
+ const [progressVisible, setProgressVisible] = useState(false)
+ const [progressRate, setProgressRate] = useState(0)
+ const { setFilePath, setFileName, currentKey, renderChilderByTabKey, setTabSelect, setBaseInfo } = props;
+ const intl = useIntl();
+ const [tableFilters, setTableFilters] = useState()
+ const [isSameFolder, setIsSameFolder] = useState(true)
+
+ const columns = [
+ {
+ title: intl.formatMessage({ id: 'tsviewer.fileSelect.file', }),
+ dataIndex: 'name',
+ key: 'name',
+ render(text, record, index) {
+ if (record.type == 'DIRECTORY') {
+ return <><FolderOpenTwoTone /> {record.name}</>
+ } else {
+ return <><FileTwoTone /> {record.name}</>
+ }
+ }
+ },
+ // {
+ // title: 'FileType',
+ // dataIndex: 'type',
+ // key: 'type',
+ // render: (text) => {
+ // if (text == 'DIRECTORY') {
+ // return 'DIRECTORY';
+ // } else {
+ // return 'FILE'
+ // }
+ // }
+ // },
+ // {
+ // title: 'CanRead',
+ // dataIndex: 'canRead',
+ // key: 'canRead',
+ // render: (text) => {
+ // if (text == true) {
+ // return 'Y';
+ // } else {
+ // return 'N'
+ // }
+ // }
+ // },
+ {
+ title: intl.formatMessage({ id: 'tsviewer.fileSelect.status', }),
+ dataIndex: 'status',
+ key: 'status',
+ filters: [
+ { text: intl.formatMessage({ id: 'tsviewer.fileSelect.loaded', }), value: "LOADED" },
+ ],
+ render(text, record, index) {
+ if (record.status == 'EXCLUDED') {
+ return
+ } else if (record.status == 'UNLOAD') {
+ return intl.formatMessage({ id: 'tsviewer.fileSelect.unload', })
+ } else if (record.status == 'LOADED') {
+ return intl.formatMessage({ id: 'tsviewer.fileSelect.loaded', })
+ } else {
+ return record.status
+ }
+ }
+ },
+ {
+ title: intl.formatMessage({ id: 'tsviewer.fileSelect.operation', }),
+ key: 'operation',
+ render: (text, record, index) => {
+ let operationValue;
+ if (record.type == 'DIRECTORY') {
+ operationValue = (
+ <>
+ <a onClick={() => {
+ let b = breadArray
+ b.push(record.name)
+ openDrictory(b)
+ }}>{intl.formatMessage({ id: 'tsviewer.fileSelect.accessDirectory', })}</a>
+ </>
+ )
+ } else if (record.status == 'UNLOAD') {
+ operationValue = (<>
+ <a onClick={() => { loadFile(record.name, record.sameFolder) }}>{intl.formatMessage({ id: 'tsviewer.fileSelect.loadFile', })}</a>
+ </>)
+ } else {
+ operationValue = (<>
+ <a onClick={() => { loadFile(record.name, record.sameFolder) }}>{intl.formatMessage({ id: 'tsviewer.fileSelect.openFile', })}</a>
+ <a onClick={() => { unLoadFile(record.name, record.sameFolder) }}>{intl.formatMessage({ id: 'tsviewer.fileSelect.unloadFile', })}</a>
+ </>)
+ }
+ return (
+ <Space size="middle">
+ {operationValue}
+ </Space>
+ )
+ }
+ },
+
+ ];
+
+ const handleClick = () => {
+ setVisible(true)
+ }
+ const handleCancel = () => {
+ setVisible(false);
+ setProgressVisible(false);
+ };
+
+ const openDrictory = async (newBreadArray) => {
+ let path = directoryPath;
+ let arr = Object.values(newBreadArray);
+ arr.map(str => {
+ if (path == '') {
+ path = str
+ } else {
+ path = path + "/" + str;
+ }
+ })
+
+ let res = await showFileListUsingPOST({ directoryPath: path });
+ if (res.code == 0) {
+ setTableData(res.data)
+ setBreadArray(arr)
+ 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></>;
+ }))
+ } else {
+ notification.error({
+ message: res.message,
+ });
+ }
+ }
+
+ const generateBreadArray = (param, key) => {
+ let array = []
+ for (var i = 0; i <= key; i++) {
+ array.push(param[i])
+ }
+ return array
+ }
+
+ const loadFile = async (name, sameFolder) => {
+ setProgressRate(0)
+ let path = directoryPath;
+ let arr = Object.values(breadArray);
+ arr.map(str => {
+ if (path == '') {
+ path = str
+ } else {
+ path = path + "/" + str;
+ }
+ })
+ if (path == '') {
+ path = name;
+ } else {
+ path = path + "/" + name;
+ }
+ setFilePath(path)
+ setFileName(name)
+ setProgressTitle(name)
+ setProgressVisible(true)
+ if (currentKey == undefined) {
+ renderChilderByTabKey("overview", true)
+ } else {
+ renderChilderByTabKey(currentKey, true)
+ }
+ let res = await loadFileUsingPOST({ filePath: path });
+ if (res.code == 0) {
+ //刷新table
+ if (sameFolder) {
+ openDrictory(breadArray)
+ } else {
+ filtersResult();
+ }
+
+ let intervalId = setInterval(() => { loadingProcessor(path, name, intervalId) }, 1000)
+ } else {
+ setProgressVisible(false)
+ notification.error({
+ message: res.message,
+ });
+ }
+ }
+
+ const loadingProcessor = async (path, name, intervalId) => {
+ let res = await getProcessUsingPOST({ filePath: path });
+ if (res.code == 0) {
+ setProgressRate(res.data.rateOfProcess)
+ if (res.data.rateOfProcess == 100) {
+ let res1 = await getBaseinfoUsingPOST({ filePath: path });
+ if (res1.code == 0) {
+ setBaseInfo({
+ version: res1.data.version, metadataSize: res1.data.metadataSize,
+ chunkGroupList: res1.data.chunkGroupList, timeseriesIndexList: res1.data.timeseriesIndexList
+ })
+ }
+ clearInterval(intervalId)
+ handleCancel()
+ notification.success({ message: name + intl.formatMessage({ id: 'tsviewer.opened', }) })
+ }
+ } else {
+ notification.error({
+ message: res.message,
+ });
+ }
+ }
+
+
+
+ const unLoadFile = async (name, sameFolder) => {
+ let path = directoryPath;
+ let arr = Object.values(breadArray);
+ arr.map(str => {
+ if (path == '') {
+ path = str
+ } else {
+ path = path + "/" + str;
+ }
+ })
+ if (path == '') {
+ path = name;
+ } else {
+ path = path + "/" + name;
+ }
+ let res = await unLoadFileUsingPOST({ filePath: path });
+ if (res.code == 0) {
+ setFileName('')
+ setFilePath('')
+ setTabSelect('')
+ //刷新table
+ if (sameFolder) {
+ openDrictory(breadArray)
+ } else {
+ filtersResult();
+ }
+ notification.success({
+ message: path + intl.formatMessage({ id: 'tsviewer.fileSelect.fileUnloaded', }),
+ })
+ } else {
+ notification.error({
+ message: res.message,
+ });
+ }
+ }
+
+ const handelTableChanged = async (pagenation, filters) => {
+ setTableFilters(filters)
+ setIsSameFolder(false)
+ }
+
+ const filtersResult = async () => {
+ if (tableFilters == undefined || tableFilters.status == null) {
+ openDrictory([])
+ return
+ }
+ let res = await showLoadedFileListUsingPOST();
+ if (res.code == 0) {
+ setBreadArray([])
+ setBreadData()
+ setTableData(res.data)
+ } else {
+ notification.error({
+ message: res.message,
+ });
+ }
+ }
+
+ useEffect(() => {
+ //刷新table
+ if (isSameFolder) {
+ openDrictory(breadArray)
+ } else {
+ filtersResult();
+ }
+ }, [tableFilters])
+
+ return (
+ <>
+ <Button key="1" type="primary" onClick={() => handleClick()}>{intl.formatMessage({ id: 'tsviewer.fileSelect.fileManagement', })}</Button>
+ <Modal
+ title={<>
+ <Tooltip placement="bottom" title={<span>
+ {intl.formatMessage({ id: 'tsviewer.fileManagement.explanation', })}<br />
+ {intl.formatMessage({ id: 'tsviewer.fileManagement.explanation1', })}<br />
+ </span>}>
+ <QuestionCircleOutlined />
+ </Tooltip>
+ <span>
+ {"\u00A0\u00A0" + intl.formatMessage({ id: 'tsviewer.fileSelect.fileManagement', })}
+ </span>
+ </>}
+ centered
+ visible={visible}
+ onCancel={handleCancel}
+ maskClosable={false}
+ width={"70%"}
+
+ footer={[]}
+ >
+ {/* <Row align="middle" justify="center">
+ <Col span={1} style={{ margin: '5px 0px 0px 0px' }}>{intl.formatMessage({ id: 'tsviewer.fileSelect.path', })}:</Col>
+ <Col span={21}><Input placeholder="Path" onChange={(e) => { setDirectoryPath(e.target.value) }} /></Col>
+ <Col span={2}><Button key="accessPath" type='primary' onClick={() => { openDrictory([]) }}>{intl.formatMessage({ id: 'tsviewer.fileSelect.openPath', })}</Button></Col>
+ </Row> */}
+ <Breadcrumb style={{ margin: '0px 0px 0px 0px' }} separator="">
+ <div style={{ cursor: "pointer", background: "#f7e0e0" }}><Breadcrumb.Item onClick={() => { openDrictory([]) }}>{intl.formatMessage({ id: 'tsviewer.fileSelect.path', })}</Breadcrumb.Item></div>
+ <Breadcrumb.Separator>{'>'}</Breadcrumb.Separator>
+ {breadData}
+ </Breadcrumb>
+ <div style={{ height: "65vh" }}>
+ <Table columns={columns} dataSource={tableData} onChange={handelTableChanged}
+ pagination={{ defaultPageSize: 50, showQuickJumper: true, position: ["bottomCenter"] }} bordered scroll={{ y: '53vh' }} />
+ </div>
+ </Modal>
+
+ <Modal
+ title={intl.formatMessage({ id: 'tsviewer.fileSelect.fileName', }) + progressTitle}
+ centered
+ visible={progressVisible}
+ onCancel={handleCancel}
+ maskClosable={false}
+ closable={false}
+ width={"50%"}
+ footer={[]}
+ >
+ <Progress style={{ margin: '25px 0px 0px 0px' }} percent={progressRate} />
+ </Modal>
+
+ </>
+ );
+}
+
+export default FileSelect
\ No newline at end of file
diff --git a/tsfile-viewer-web-frontend/src/pages/tsfile-tool/v2/index.jsx b/tsfile-viewer-web-frontend/src/pages/tsfile-tool/v2/index.jsx
new file mode 100644
index 0000000..a18a48f
--- /dev/null
+++ b/tsfile-viewer-web-frontend/src/pages/tsfile-tool/v2/index.jsx
@@ -0,0 +1,100 @@
+/*
+ * 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.
+ */
+import React, { useState } from "react";
+import { notification, Tooltip } from 'antd';
+import { GridContent, PageContainer, RouteContext } from '@ant-design/pro-layout';
+import FileSelect from "./fileSelect/fileSelect";
+import Overview from "./overview/overview";
+import { QuestionCircleOutlined } from '@ant-design/icons';
+import SearchPageData from "./searchData/searchPageData"
+import { useIntl } from 'umi';
+
+const v2 = (props) => {
+ const [fileName, setFileName] = useState('')
+ const [filePath, setFilePath] = useState('')
+ const [currentKey, setCurrentKey] = useState()
+ const [tabSelect, setTabSelect] = useState()
+ const [baseInfo, setBaseInfo] = useState({ version: "", metadataSize: "", chunkGroupList: [], timeseriesIndexList: [] })
+ const intl = useIntl();
+
+ const renderChilderByTabKey = (key, skipFileNameCheck) => {
+ setCurrentKey(key)
+ if (fileName == '' || fileName == undefined) {
+ if (!skipFileNameCheck) {
+ return;
+ }
+ }
+ setTabSelect(key)
+ }
+
+ return (
+ <div
+ style={{
+ background: '#F5F7FA',
+ }}
+ >
+ <PageContainer
+ fixedHeader
+ header={{
+ title: (
+ <div style={{ whiteSpace: "pre-wrap" }}>
+ <Tooltip placement="bottom" title={<span>
+ {intl.formatMessage({ id: 'tsviewer.index.explanation', })}<br/>
+ {intl.formatMessage({ id: 'tsviewer.index.explanation1', })}<br/>
+ {intl.formatMessage({ id: 'tsviewer.index.explanation2', })}<br/>
+ </span>}>
+ <QuestionCircleOutlined />
+ </Tooltip>
+ {"\u00A0\u00A0"+filePath}
+ </div>
+ ),
+ ghost: true,
+ extra: [
+ <FileSelect setFileName={setFileName} setFilePath={setFilePath} currentKey={currentKey}
+ renderChilderByTabKey={renderChilderByTabKey} setTabSelect={setTabSelect} setBaseInfo={setBaseInfo}></FileSelect>
+ ],
+ }}
+ tabList={[
+ {
+ tab: intl.formatMessage({ id: 'tsviewer.overview', }),
+ key: 'overview',
+ closable: false,
+ },
+ {
+ tab: intl.formatMessage({ id: 'tsviewer.search', }),
+ key: 'search',
+ closable: false,
+ },
+ ]}
+ tabProps={{
+ type: 'line',
+ onChange: (s) => renderChilderByTabKey(s),
+
+ }}
+ >
+ <div id="overview" style={{ display: tabSelect == 'overview' ? 'block' : 'none', background: "white", height: "100vh" }}>
+ <Overview fileName={fileName} filePath={filePath} baseInfo={baseInfo}></Overview>
+ </div>
+ <div id="search" style={{ display: tabSelect == 'search' ? 'block' : 'none', background: "white", height: "100vh" }}>
+ <SearchPageData filePath={filePath}></SearchPageData>
+ </div>
+ </PageContainer>
+ </div>
+ )
+}
+
+export default v2
\ No newline at end of file
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
new file mode 100644
index 0000000..6dd90f1
--- /dev/null
+++ b/tsfile-viewer-web-frontend/src/pages/tsfile-tool/v2/overview/moreChunkGroup.jsx
@@ -0,0 +1,400 @@
+/*
+ * 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.
+ */
+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 {
+ getChunkGroupsListUsingPOST, getChunkGroupInfoUsingPOST, getChunkListUsingPOST, getPageListUsingPOST
+ , getPageInfoUsingPOST
+} from '@/services/swagger1/tsfileViewerController'
+import moment from 'moment';
+import { useIntl } from 'umi';
+
+const { Header, Footer, Sider, Content } = Layout;
+const { Search } = Input;
+
+const MoreChunkGroup = (props) => {
+
+ const [details, setDetails] = useState()
+ const [openChunk, setopenChunk] = useState(false)
+ const [openPage, setopenPage] = useState(false)
+ const [pageSize, setPageSize] = useState(10)
+ const [pageNo, setPageNo] = useState(1)
+ const [totalSize, setTotalSize] = useState()
+ const [deviceNameLike, setDeviceNameLike] = useState()
+ const [treeData, setTreeData] = useState([])
+ const [columns, setColumns] = useState();
+ const [columnsLength, setColumnsLength] = useState();
+ const [pageData, setPageData] = useState()
+ const intl = useIntl();
+
+ var pageDataCache;
+
+ const { fileName, filePath, cardList, setCardList } = props;
+
+ const gridStyle = {
+ width: '100%',
+ background: "#f2f2f2",
+ };
+
+ const showChunkGroupDetails = async (cg) => {
+ let info = JSON.parse(cg)
+ let res = await getChunkGroupInfoUsingPOST({ offset: info.offset, filePath: filePath })
+ if (res.code == 0) {
+ let data = res.data;
+ data['offset'] = info.offset;
+ setDetails(data)
+ } else {
+ notification.error({
+ message: res.message,
+ });
+ }
+ }
+
+ const showChunk = async () => {
+ if (details == undefined) {
+ return
+ }
+ let res = await getChunkListUsingPOST({ offset: details.offset, filePath: filePath, offsetType: 'CG' })
+ if (res.code == 0) {
+ setTreeData(Object.values(res.data).map((chunkInfo) => {
+ let tree = {};
+ tree['title'] = chunkInfo.measurementId;
+ tree['key'] = chunkInfo.offset;
+ tree['icon'] = <GroupOutlined />
+ tree['isLeaf'] = false;
+ return tree;
+ }))
+ setopenChunk(true);
+ } else {
+ notification.error({
+ message: res.message,
+ });
+ }
+
+ };
+
+ 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);
+ };
+
+ const showPage = () => {
+ setopenPage(true);
+ };
+
+ const onClosePage = () => {
+ 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
+ }
+
+ }]
+ 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 (
+ <>
+ <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]
+ }
+ }
+ }
+ }))
+ setColumnsLength(cols.length)
+ setColumns(cols)
+ setPageData(res.data.values);
+ pageDataCache = res.data.values;
+ showPage()
+ } else {
+ notification.error({
+ message: res.message,
+ });
+ }
+ }
+ };
+
+ const Details = () => {
+ const show = () => {
+ if (details != undefined && details != '') {
+ return (
+ <pre style={{ height: "55vh", overflow: "auto", whiteSpace: "pre-wrap" }}>
+ {"ChunkGroup " + intl.formatMessage({ id: 'tsviewer.more.briefInfo', }) + ":\n"}
+ {"offset:"}{details.offset + "\n"}
+ {"CGH:\n"}{JSON.stringify(details.cgh, null, '\t')}{"\n"}
+ {"FIRST CH:\n"}{JSON.stringify(details.ch, null, '\t')}{"\n"}
+ {"FIRST PH:\n"}{JSON.stringify(details.ph, null, '\t')}{"\n"}</pre>
+ )
+ }
+ }
+ return (
+ <div style={{ background: "#f2f2f2", padding: 20, height: "100%" }}>
+ {show()}
+ </div>
+ )
+ }
+
+ const generateChunkGroupCards = async (page, pSize) => {
+ let res = await getChunkGroupsListUsingPOST({ filePath: filePath, pageNo: page, pageSize: pSize, deviceLike: deviceNameLike });
+ if (res.code == 0) {
+ let list = Object.values(res.data.pageItems)
+ setTotalSize(res.data.totalCount)
+ setDetails()
+ setCardList(list.map((cg, key) => {
+ return (
+ <div style={{ backgroud: "white", margin: '4px 0px 0px 0px' }}>
+ <Card.Grid key={key} id={"cardgrid" + key}
+ style={gridStyle}
+ hoverable={false}
+ onClick={() => { selectCard("cardgrid" + key), showChunkGroupDetails(JSON.stringify(cg)) }}>
+ <strong>{cg.deviceName}</strong>
+ </Card.Grid>
+ </div>
+
+ )
+ }))
+ setPageNo(page)
+ } else {
+ notification.error({
+ message: res.message,
+ });
+ }
+ }
+ //记录card选中项
+ var cardSelect;
+ const selectCard = (id) => {
+ if (id == cardSelect) {
+ return
+ }
+ var card = document.getElementById(id);
+ card.style.background = "#FC4C2F"
+ var oldCard = document.getElementById(cardSelect);
+ if (oldCard != null) {
+ oldCard.style.background = "#f2f2f2";
+ }
+ cardSelect = id;
+ }
+
+ useEffect(() => {
+ generateChunkGroupCards(1, pageSize);
+ }, [])
+
+ return (
+ <div className={styles.sitedrawerrenderincurrentwrapper}>
+ <Layout>
+ <Sider width="55%" style={{ height: "70vh", width: "60%", background: "white", padding: 5 }}>
+ <div style={{ height: "70vh", overflow: "auto" }}>
+ <PageHeader
+ extra={(
+ <Search
+ placeholder="deviceName"
+ allowClear
+ onChange={(e) => { setDeviceNameLike(e.target.value) }}
+ onSearch={() => { generateChunkGroupCards(1, pageSize) }}
+ style={{
+ width: 500,
+ }}
+ />
+ )}>
+ </PageHeader>
+ <Card style={{ height: "54vh", overflow: "auto" }}>
+ {cardList}
+ </Card>
+ <div style={{ width: "100%", textAlign: "center" }}>
+ <Pagination style={{ margin: '20px 0px 0px 0px' }} current={pageNo} pageSize={pageSize} total={totalSize}
+ showQuickJumper={true}
+ onChange={(page, pSize) => { setCardList([]), generateChunkGroupCards(page, pSize) }} showSizeChanger={false} />
+ </div>
+ </div>
+ </Sider>
+ <Layout>
+ <Content width="40%" style={{ height: "55vh", background: "white" }}>
+ <PageHeader
+ style={{ background: "#f2f2f2" }}
+ extra={(
+ <Button type="primary" onClick={() => showChunk(details)}>{intl.formatMessage({ id: 'tsviewer.more.structureMap', })}</Button>
+ )}>
+ </PageHeader>
+ <Details></Details>
+ </Content>
+ </Layout>
+ </Layout>
+ <Drawer
+ title={<>
+ <Tooltip placement="bottom" title={<span>
+ {intl.formatMessage({ id: 'tsviewer.moreChunkGroup.chunk.explanation', })}<br />
+ {intl.formatMessage({ id: 'tsviewer.moreChunkGroup.chunk.explanation1', })}<br />
+ {intl.formatMessage({ id: 'tsviewer.moreChunkGroup.chunk.explanation2', })}<br />
+ </span>}>
+ <QuestionCircleOutlined />
+ </Tooltip>
+ <span>
+ {"\u00A0\u00A0 ChunkInfo"}
+ </span>
+ </>}
+ width={"80%"}
+ closable={false}
+ destroyOnClose={true}
+ onClose={onCloseChunk}
+ open={openChunk}
+ >
+ <div style={{ height: "80vh", overflow: "auto" }}>
+ <Tree
+ showLine={{ showLeafIcon: false }}
+ showIcon={true}
+ onSelect={onSelect}
+ loadData={onLoadData}
+ treeData={treeData}
+ />
+ </div>
+
+ <Drawer
+ title={<>
+ <Tooltip placement="bottom" title={
+ <span>
+ {intl.formatMessage({ id: 'tsviewer.moreChunkGroup.pageData.explanation', })}<br />
+ </span>}>
+ <QuestionCircleOutlined />
+ </Tooltip>
+ <span>
+ {"\u00A0\u00A0 PageData"}
+ </span>
+ </>}
+ width={"75%"}
+ closable={false}
+ destroyOnClose={true}
+ onClose={onClosePage}
+ open={openPage}
+ >
+ <Table columns={columns} dataSource={pageData} scroll={{ x: 150 * columnsLength, y: "80vh" }}
+ rowKey={(record)=>{
+ return record[0];
+ }}
+ pagination={{ pageSize: 100, showQuickJumper: true, position: ["bottomCenter"] }}
+ bordered />
+ </Drawer>
+ </Drawer>
+
+ </div >
+ )
+}
+
+export default MoreChunkGroup
\ No newline at end of file
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
new file mode 100644
index 0000000..e76b266
--- /dev/null
+++ b/tsfile-viewer-web-frontend/src/pages/tsfile-tool/v2/overview/moreIndexOfTimeseriesIndex.jsx
@@ -0,0 +1,485 @@
+/*
+ * 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.
+ */
+import React, { useState, useEffect } from "react";
+import { Card, Layout, Button, Drawer, Tree, Input, Tooltip, DatePicker, PageHeader, notification, Table } from 'antd';
+import { Tree as TreeArborist } from "react-arborist";
+import styles from '../style.less'
+import { RightOutlined, GroupOutlined, MinusSquareOutlined, PlusSquareOutlined, CopyOutlined, RetweetOutlined, QuestionCircleOutlined } from '@ant-design/icons';
+import {
+ getChunkListUsingPOST, getPageListUsingPOST, getIndexOfTimeseriesIndexTreeUsingPOST
+ , getPageInfoThroughTimeseriesIndexOffsetUsingPOST, getTimeseriesIndexListNoPagingUsingPOST
+} from '@/services/swagger1/tsfileViewerController'
+import moment from 'moment';
+import { useIntl } from 'umi';
+
+const { Content } = Layout;
+const { Search } = Input;
+
+const MoreIndexOfTimeseriesIndex = (props) => {
+ const [openChunk, setopenChunk] = useState(false)
+ const [openPage, setopenPage] = useState(false)
+ const [chunkTreeData, setChunkTreeData] = useState([])
+ const [indexTreeData, setIndexTreeData] = useState([])
+ const [columns, setColumns] = useState();
+ const [columnsLength, setColumnsLength] = useState();
+ const [pageData, setPageData] = useState()
+ const [beginDateCache, setBeginDateCache] = useState()
+ const [endDateCache, setEndDateCache] = useState();
+ const [beginDate, setBeginDate] = useState()
+ const [endDate, setEndDate] = useState();
+ const [deviceNameLike, setDeviceNameLike] = useState()
+ const [randomFlag, setRandomFlag] = useState()
+ const [indexTreeHeight, setIndexTreeHeight] = useState()
+ const { fileName, filePath, cardList, setCardList } = props;
+ const intl = useIntl();
+
+ var pageDataCache;
+
+ const gridStyle = {
+ width: '20%',
+ height: '100px',
+ textAlign: 'center',
+ background: "#f2f2f2",
+ border: '1px solid grey'
+ };
+
+
+ const showChunk = async (node) => {
+ if (!node.isLeaf) {
+ return
+ }
+ 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) => {
+ let tree = {};
+ tree['title'] = chunkInfo.measurementId;
+ tree['key'] = chunkInfo.offset;
+ tree['icon'] = <GroupOutlined />
+ tree['timeseriesIndexOffset'] = node.position;
+ tree['isLeaf'] = false;
+ return tree;
+ }))
+ setopenChunk(true);
+ } else {
+ notification.error({
+ message: res.message,
+ });
+ }
+ };
+
+ 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
+ }
+
+ }]
+ 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 (
+ <>
+ <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]
+ }
+ }
+ }
+ }))
+ setColumnsLength(cols.length)
+ setColumns(cols)
+ setPageData(res.data.values);
+ pageDataCache = res.data.values;
+ showPage()
+ } else {
+ notification.error({
+ message: res.message,
+ });
+ }
+ }
+ };
+
+ const onCloseChunk = () => {
+ setopenChunk(false);
+ };
+
+ const showPage = () => {
+ setopenPage(true);
+ };
+
+ const onClosePage = () => {
+ setopenPage(false);
+ };
+
+ const updateIndexTreeData = (list, id, children) => {
+ return list.map((node) => {
+ if (node.id === id) {
+ return {
+ ...node,
+ children,
+ };
+ }
+ //这个应该是用来加载子节点的子节点的,应该需要修改children对象
+ if (node.children) {
+ return {
+ ...node,
+ children: updateIndexTreeData(node.children, id, children),
+ };
+ }
+ return node;
+ });
+ }
+
+ 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 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) => {
+ if (expanded && node.children == undefined) {
+ let res = await getIndexOfTimeseriesIndexTreeUsingPOST({ parentOffset: node.position, filePath: filePath })
+ if (res.code == 0) {
+ let newTree = Object.values(res.data).map((child) => {
+ let tree = {};
+ tree['name'] = child.title;
+ tree['id'] = child.measurementId + '-' + child.position + randomFlag;
+ // tree['icon'] = <RightOutlined />
+ tree['isLeaf'] = child.leaf;
+ 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)
+ setIndexTreeData(pa)
+ } else {
+ notification.error({
+ message: res.message,
+ });
+ }
+ }
+ }
+
+ const generateITITrees = async () => {
+ let res = await getIndexOfTimeseriesIndexTreeUsingPOST({ parentOffset: 0, filePath: filePath })
+ if (res.code == 0) {
+ setRandomFlag(moment(new Date()).valueOf())
+ let tree = Object.values(res.data).map((node) => {
+ let tree = {};
+ tree['name'] = node.title;
+ tree['id'] = node.title + '-' + node.position + randomFlag;
+ // tree['icon'] = <RightOutlined />
+ tree['isLeaf'] = node.leaf;
+ tree['position'] = node.position;
+ return tree;
+ })
+ setIndexTreeData(tree)
+ } else {
+ notification.error({
+ message: res.message,
+ });
+ }
+ }
+
+ function Node({ node, style, dragHandle, tree }) {
+ if (!node.isOpen && !node.data.isLeaf && node.data.children == undefined && node.isSelected) {
+ onLoadIndexTreeData(!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={() => (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>
+ </div>
+ );
+ }
+
+ const searchIndexTree = async (value) => {
+ let res = await getTimeseriesIndexListNoPagingUsingPOST({
+ filePath: filePath, deviceNameLike: value,
+ beginDate: beginDate, endDate: endDate
+ });
+ let treeList = []
+ if (res.code == 0) {
+ for (let item in res.data) {
+ let tree = {};
+ tree['name'] = item;
+ tree['id'] = item + randomFlag;
+ // tree['icon'] = <RightOutlined />
+ tree['isLeaf'] = false;
+ tree['children'] = Object.values(res.data[item]).map((arr, key) => {
+ let node = {};
+ node['name'] = arr.measurementId;
+ node['id'] = arr.measurementId + arr.offset + randomFlag;
+ // node['icon'] = <RightOutlined />
+ node['isLeaf'] = true;
+ node['position'] = arr.offset;
+ return node;
+ })
+ treeList.push(tree);
+ }
+ setIndexTreeData(treeList)
+ } else {
+ notification.error({
+ message: res.message,
+ });
+ }
+ }
+
+ const onSearchIndexTree = async (value) => {
+ //暂存 开始和结束时间
+ setBeginDateCache(beginDate)
+ setEndDateCache(endDate)
+ if ((beginDate == null || beginDate == undefined || beginDate == '') && (endDate == null || endDate == undefined || endDate == '') && (value == null || value == undefined || value == '')) {
+ generateITITrees();
+ } else {
+ searchIndexTree(value);
+ }
+ }
+
+ useEffect(() => {
+ var div = document.getElementById('modal-div');
+ setIndexTreeHeight(div.offsetHeight)
+ generateITITrees();
+ }, [])
+
+ return (
+ <div className={styles.sitedrawerrenderincurrentwrapper}>
+ <Layout>
+ <Layout>
+ <Content width="40%" style={{ height: "60vh" }}>
+ <PageHeader style={{ background: "white" }}
+ extra={(
+ <Button.Group>
+ <DatePicker
+ format='YYYY-MM-DD HH:mm:ss'
+ placeholder={intl.formatMessage({ id: 'tsviewer.more.beginDate', })}
+ showTime={{ format: 'HH:mm:ss' }}
+ onChange={(date) => {
+ if (date != null) {
+ date = date.set({ millisecond: 0 })
+ }
+ setBeginDate(isNaN(moment(date).valueOf()) ? '' : moment(date).valueOf())
+ }}
+ />
+
+ <DatePicker
+ format='YYYY-MM-DD HH:mm:ss'
+ placeholder={intl.formatMessage({ id: 'tsviewer.more.endDate', })}
+ showTime={{ format: 'HH:mm:ss' }}
+ onChange={(date) => {
+ if (date != null) {
+ date = date.set({ millisecond: 0 })
+ }
+ setEndDate(isNaN(moment(date).valueOf()) ? '' : moment(date).valueOf())
+ }}
+ />
+ <Search
+ placeholder="device"
+ allowClear
+ onChange={(e) => {
+ setDeviceNameLike(e.target.value)
+ }}
+ onSearch={(value) => onSearchIndexTree(value)}
+ style={{
+ width: 400,
+ }}
+ />
+ {/* <Button type="primary" onClick={() => showChunk(details)}>结构图谱</Button> */}
+ </Button.Group>
+ )}
+ >
+ </PageHeader>
+ <div id="modal-div" style={{ height: "60vh", background: "white", margin: '4px 0px 0px 0px' }}>
+ <TreeArborist
+ openByDefault={false}
+ disableDrag={false}
+ width={"100%"}
+ height={indexTreeHeight}
+ // paddingBottom={200}
+ //height={400}
+ data={indexTreeData}
+ >
+ {Node}
+ </TreeArborist>
+ </div>
+ </Content>
+ </Layout>
+ </Layout>
+ <Drawer
+ title={<>
+ <Tooltip placement="bottom" title={<span>
+ {intl.formatMessage({ id: 'tsviewer.moreChunkGroup.chunk.explanation', })}<br />
+ {intl.formatMessage({ id: 'tsviewer.moreChunkGroup.chunk.explanation1', })}<br />
+ {intl.formatMessage({ id: 'tsviewer.moreChunkGroup.chunk.explanation2', })}<br />
+ </span>}>
+ <QuestionCircleOutlined />
+ </Tooltip>
+ <span>
+ {"\u00A0\u00A0 ChunkInfo"}
+ </span>
+ </>}
+ width={"80%"}
+ closable={false}
+ destroyOnClose={true}
+ 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>
+
+ <Drawer
+ title={<>
+ <Tooltip placement="bottom" title={<span>
+ {intl.formatMessage({ id: 'tsviewer.moreChunkGroup.pageData.explanation', })}<br />
+ </span>}>
+ <QuestionCircleOutlined />
+ </Tooltip>
+ <span>
+ {"\u00A0\u00A0 PageData"}
+ </span>
+ </>}
+ width={"75%"}
+ closable={false}
+ destroyOnClose={true}
+ onClose={onClosePage}
+ open={openPage}
+ >
+ <Table columns={columns} dataSource={pageData} scroll={{ x: 150 * columnsLength, y: "80vh" }}
+ rowKey={(record) => {
+ return record[0];
+ }}
+ pagination={{ defaultPageSize: 100, showQuickJumper: true, position: ["bottomCenter"] }}
+ bordered />
+ </Drawer>
+ </Drawer>
+
+ </div>
+ )
+}
+
+export default MoreIndexOfTimeseriesIndex
\ No newline at end of file
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
new file mode 100644
index 0000000..e145a7f
--- /dev/null
+++ b/tsfile-viewer-web-frontend/src/pages/tsfile-tool/v2/overview/moreTimeseriesIndex.jsx
@@ -0,0 +1,444 @@
+/*
+ * 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.
+ */
+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 {
+ getTimeseriesIndexListUsingPOST, getChunkListUsingPOST, getPageListUsingPOST
+ , getPageInfoThroughTimeseriesIndexOffsetUsingPOST, getTimeseriesIndexInfoUsingPOST
+} from '@/services/swagger1/tsfileViewerController'
+import moment from 'moment';
+import { useIntl } from 'umi';
+
+const { Sider, Content } = Layout;
+const { Search } = Input;
+
+const MoreTimeseriesIndex = (props) => {
+
+ const [details, setDetails] = useState()
+ const [openChunk, setopenChunk] = useState(false)
+ const [openPage, setopenPage] = useState(false)
+ const [currentTimeseriesIndex, setCurrentTimeseriesIndex] = useState()
+
+ const [pageSize, setPageSize] = useState(10)
+ const [pageNo, setPageNo] = useState(1)
+ const [totalSize, setTotalSize] = useState()
+ const [treeData, setTreeData] = useState([])
+ const [columns, setColumns] = useState();
+ const [columnsLength, setColumnsLength] = useState();
+ const [pageData, setPageData] = useState()
+
+ const [beginDateCache, setBeginDateCache] = useState()
+ const [endDateCache, setEndDateCache] = useState();
+ const [beginDate, setBeginDate] = useState()
+ const [endDate, setEndDate] = useState();
+ const [deviceNameLike, setDeviceNameLike] = useState()
+ const { fileName, filePath, cardList, setCardList } = props;
+ const intl = useIntl();
+
+ var pageDataCache;
+
+ const gridStyle = {
+ width: '100%',
+ background: "#f2f2f2",
+ };
+
+
+ const showTimeseriesIndexDetails = async (ti) => {
+ let info = JSON.parse(ti)
+ setCurrentTimeseriesIndex(info.measurementId);
+ let res = await getTimeseriesIndexInfoUsingPOST({ offset: info.offset, filePath: filePath })
+ if (res.code == 0) {
+ let data = res.data;
+ data['offset'] = info.offset;
+ setDetails(data)
+ } else {
+ notification.error({
+ message: res.message,
+ });
+ }
+ }
+
+ const showChunk = async (props) => {
+ if (details == undefined) {
+ return
+ }
+ 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) => {
+ let tree = {};
+ tree['title'] = chunkInfo.measurementId;
+ tree['key'] = chunkInfo.offset;
+ tree['icon'] = <GroupOutlined />
+ tree['isLeaf'] = false;
+ return tree;
+ }))
+ setopenChunk(true);
+ } else {
+ notification.error({
+ message: res.message,
+ });
+ }
+ };
+
+ 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, 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 onCloseChunk = () => {
+ setopenChunk(false);
+ };
+
+ const showPage = () => {
+ setopenPage(true);
+ };
+
+ const onClosePage = () => {
+ 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: (
+ <>
+ {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]
+ }
+ }
+ }
+ }))
+ setColumnsLength(cols.length)
+ setColumns(cols)
+ setPageData(res.data.values);
+ pageDataCache = res.data.values;
+ showPage()
+ } else {
+ notification.error({
+ message: res.message,
+ });
+ }
+ }
+ };
+
+ const Details = (props) => {
+
+ const show = () => {
+ if (details != undefined && details != '') {
+ return (
+ <pre style={{ height: "55vh", overflow: "auto", whiteSpace: "pre-wrap" }}>
+ {"TimeseriesIndex +" + intl.formatMessage({ id: 'tsviewer.more.briefInfo', }) + "+:\n"}
+ {"offset:"}{details.offset + "\n"}
+ {"MEASUREMENTIDS:"}{currentTimeseriesIndex}{"\n"}
+ {"TM:\n"}{JSON.stringify(details.tm, null, '\t')}{"\n"}
+ {"FIRST CM:\n"}{JSON.stringify(details.cm, null, '\t')}{"\n"}
+ </pre>
+ )
+ }
+ }
+ return (
+ <div style={{ background: "#f2f2f2", padding: 20, height: "100%" }}>
+ {show()}
+ </div>
+ )
+ }
+
+ const generateTimeSeriesCards = async (page, pSize) => {
+ let res = await getTimeseriesIndexListUsingPOST({
+ filePath: filePath, pageNo: page, pageSize: pSize, deviceNameLike: deviceNameLike,
+ beginDate: beginDate, endDate: endDate
+ });
+ //暂存 开始和结束时间
+ setBeginDateCache(beginDate)
+ setEndDateCache(endDate)
+ //清除上一条数据状态
+ setDetails()
+ if (res.code == 0) {
+ let list = Object.values(res.data.pageItems)
+ setTotalSize(res.data.totalCount)
+ setCardList(list.map((ti, key) => {
+ return (
+ <div style={{ backgroud: "white", margin: '4px 0px 0px 0px' }}>
+ <Card.Grid key={key} id={"cardgrid" + key}
+ style={gridStyle}
+ hoverable={false}
+ onClick={() => { selectCard("cardgrid" + key), showTimeseriesIndexDetails(JSON.stringify(ti)) }}>
+ <strong style={{ whiteSpace: 'pre-wrap' }}>[DEVICE]{ti.deviceId} {ti.aligned == true ? '' : '\n[MEASUREMENT]:' + ti.measurementId}</strong>
+ </Card.Grid>
+ </div>
+ )
+ }))
+ setPageNo(page)
+ } else {
+ notification.error({
+ message: res.message,
+ });
+ }
+ }
+
+ //记录card选中项
+ var cardSelect;
+ const selectCard = (id) => {
+ if (id == cardSelect) {
+ return
+ }
+ var card = document.getElementById(id);
+ card.style.background = "#FC4C2F"
+ var oldCard = document.getElementById(cardSelect);
+ if (oldCard != null) {
+ oldCard.style.background = "#f2f2f2";
+ }
+ cardSelect = id;
+ }
+
+ useEffect(() => {
+ generateTimeSeriesCards(1, pageSize);
+ }, [])
+
+ return (
+ <div className={styles.sitedrawerrenderincurrentwrapper}>
+ <Layout>
+ <Sider width="55%" style={{ height: "70vh", width: "60%", background: "white", padding: 5 }}>
+ <div style={{ height: "70vh", overflow: "auto" }}>
+ <PageHeader
+ extra={(
+ <Button.Group>
+ <DatePicker
+ format='YYYY-MM-DD HH:mm:ss'
+ placeholder={intl.formatMessage({ id: 'tsviewer.more.beginDate', })}
+ showTime={{ format: 'HH:mm:ss' }}
+ onChange={(date) => {
+ if (date != null) {
+ date = date.set({ millisecond: 0 })
+ }
+ setBeginDate(isNaN(moment(date).valueOf()) ? '' : moment(date).valueOf())
+ }}
+ />
+ <DatePicker
+ format='YYYY-MM-DD HH:mm:ss'
+ placeholder={intl.formatMessage({ id: 'tsviewer.more.endDate', })}
+ showTime={{ format: 'HH:mm:ss' }}
+ onChange={(date) => {
+ if (date != null) {
+ date = date.set({ millisecond: 0 })
+ }
+ setEndDate(isNaN(moment(date).valueOf()) ? '' : moment(date).valueOf())
+ }}
+ />
+ <Search
+ placeholder="device"
+ allowClear
+ onChange={(e) => {
+ setDeviceNameLike(e.target.value)
+ }}
+ onSearch={() => generateTimeSeriesCards(1, pageSize)}
+ style={{
+ width: 200,
+ }}
+ />
+ </Button.Group>
+ )}>
+ </PageHeader>
+ <Card style={{ height: "55vh", overflow: "auto" }}>
+ {cardList}
+ </Card>
+ <div style={{ width: "100%", textAlign: "center" }}>
+ <Pagination style={{ margin: '20px 0px 0px 0px' }} current={pageNo} pageSize={pageSize} total={totalSize}
+ showQuickJumper={true}
+ onChange={(page, pSize) => { setCardList([]), generateTimeSeriesCards(page, pSize) }} showSizeChanger={false} />
+ </div>
+ </div>
+ </Sider>
+ <Layout>
+ <Content width="40%" style={{ height: "60vh", background: "white" }}>
+ <PageHeader
+ style={{ background: "#f2f2f2" }}
+ extra={(
+ <Button type="primary" onClick={() => showChunk(details)}>{intl.formatMessage({ id: 'tsviewer.more.structureMap', })}</Button>
+ )}>
+ </PageHeader>
+ <Details></Details>
+ </Content>
+ </Layout>
+ </Layout>
+ <Drawer
+ title={<>
+ <Tooltip placement="bottom" title={<span>
+ {intl.formatMessage({ id: 'tsviewer.moreChunkGroup.chunk.explanation', })}<br />
+ {intl.formatMessage({ id: 'tsviewer.moreChunkGroup.chunk.explanation1', })}<br />
+ {intl.formatMessage({ id: 'tsviewer.moreChunkGroup.chunk.explanation2', })}<br />
+ </span>}>
+ <QuestionCircleOutlined />
+ </Tooltip>
+ <span>
+ {"\u00A0\u00A0 ChunkInfo"}
+ </span>
+ </>}
+ width={"80%"}
+ closable={false}
+ destroyOnClose={true}
+ onClose={onCloseChunk}
+ open={openChunk}
+ >
+ <div style={{ height: "80vh", overflow: "auto" }}>
+ <Tree
+ showLine={{ showLeafIcon: false }}
+ showIcon={true}
+ onSelect={onSelect}
+ loadData={onLoadData}
+ treeData={treeData}
+ />
+ </div>
+
+ <Drawer
+ title={<>
+ <Tooltip placement="bottom" title={<span>
+ {intl.formatMessage({ id: 'tsviewer.moreChunkGroup.pageData.explanation', })}<br />
+ </span>}>
+ <QuestionCircleOutlined />
+ </Tooltip>
+ <span>
+ {"\u00A0\u00A0 PageData"}
+ </span>
+ </>}
+ width={"75%"}
+ closable={false}
+ destroyOnClose={true}
+ onClose={onClosePage}
+ open={openPage}
+ >
+ <Table columns={columns} dataSource={pageData} scroll={{ x: 180 * columnsLength, y: "80vh" }}
+ rowKey={(record) => {
+ return record[0];
+ }}
+ pagination={{ defaultPageSize: 100, showQuickJumper: true, position: ["bottomCenter"] }}
+ bordered />
+ </Drawer>
+ </Drawer>
+
+ </div>
+ )
+}
+
+export default MoreTimeseriesIndex
\ No newline at end of file
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
new file mode 100644
index 0000000..80788a4
--- /dev/null
+++ b/tsfile-viewer-web-frontend/src/pages/tsfile-tool/v2/overview/overview.jsx
@@ -0,0 +1,458 @@
+/*
+ * 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.
+ */
+import React, { useState, useEffect } from "react";
+import { Layout, Col, Row, Image, notification, Tooltip } from 'antd';
+import styles from '../style.less'
+import { getChunkGroupInfoUsingPOST, getTimeseriesIndexInfoUsingPOST, getVersionUsingPOST, getMetaDataSizeUsingPOST, getMetaDataUsingPOST } from '@/services/swagger1/tsfileViewerController'
+import PopDetails from "./popDetails"
+import { useIntl } from 'umi';
+
+const { Sider, Content } = Layout;
+
+const Tsfile = (props) => {
+
+ const { fileName, baseInfo } = props;
+ const intl = useIntl();
+
+ const doMessageShow = (msg, offset) => {
+ props.showStructureContext()
+ props.doChange(msg, offset)
+ }
+ const getMessage = (wrap)=>{
+ let message;
+ if(wrap == undefined){
+ message = "";
+ }else{
+ message = wrap.deviceName +"\n[" + wrap.offset + "]";
+ }
+ return (
+ <Tooltip placement="bottom" title={<span style={{"whiteSpace":"pre-line"}}>{message}</span>}>
+ {message}
+ </Tooltip>
+ )
+ }
+ const getMessageIndex = (wrap)=>{
+ let message;
+ if(wrap == undefined){
+ message = "";
+ }else{
+ message = wrap.deviceId;
+ if(!wrap.aligned){
+ message = message + "\n"+wrap.measurementId;
+ }
+ message = message +"\n[" + wrap.offset + "]"
+ }
+ return (
+ <Tooltip placement="bottom" title={<span style={{"whiteSpace":"pre-line"}}>{message}</span>}>
+ {message}
+ </Tooltip>
+ )
+ }
+ return (
+ <>
+ <div className={styles.row}>
+ <Row gutter={[8, 8]} align="middle" justify="center" style={{ height: "50px" }}>
+ <Col span={23} style={{ height: "40px" }}>
+ <div className={styles.shortStyle} onClick={() => doMessageShow("TSFILE")}><h3 className={styles.hcenter}>TSFILE VERSION{"[" + baseInfo.version + "]"}</h3></div>
+ </Col>
+ </Row>
+ </div>
+ <div className={styles.notoplinerow}>
+ <Row gutter={[8, 8]} align="middle" justify="center" style={{ height: "200px", padding: 5 }}>
+ <Col span={6} style={{ height: "160px" }}>
+ <div className={styles.longStyle} onClick={() => doMessageShow("ChunkGroup", baseInfo.chunkGroupList[0] ? baseInfo.chunkGroupList[0].offset : undefined)}><h3 className={styles.hcenter}>{getMessage(baseInfo.chunkGroupList[0])}</h3></div>
+ </Col>
+ <Col span={6} style={{ height: "160px" }}>
+ <div className={styles.longStyle} hidden={baseInfo.chunkGroupList[1] ? false : true} onClick={() => doMessageShow("ChunkGroup", baseInfo.chunkGroupList[1] ? baseInfo.chunkGroupList[1].offset : undefined)}><h3 className={styles.hcenter}>{getMessage(baseInfo.chunkGroupList[1])}</h3></div>
+ </Col>
+ <Col span={6} style={{ height: "160px" }}>
+ <div className={styles.longStyle} hidden={baseInfo.chunkGroupList[2] ? false : true} onClick={() => doMessageShow("ChunkGroup", baseInfo.chunkGroupList[2] ? baseInfo.chunkGroupList[2].offset : undefined)}><h3 className={styles.hcenter}>{getMessage(baseInfo.chunkGroupList[2])}</h3></div>
+ </Col>
+ <Col span={6} style={{ height: "160px" }}>
+ <div className={styles.longStyle} onClick={() => doMessageShow("CGMORE")}><h3 className={styles.hcenter}>more infos</h3></div>
+ </Col>
+ </Row>
+ </div>
+
+ <div className={styles.notoplinerow}>
+ <Row gutter={[8, 8]} align="middle" justify="center" style={{ height: "200px", padding: 5 }}>
+ <Col span={6} style={{ height: "160px" }}>
+ <div className={styles.longStyle} onClick={() => doMessageShow("TimeseriesIndex", baseInfo.timeseriesIndexList[0] ? baseInfo.timeseriesIndexList[0].offset : undefined)}><h3 className={styles.hcenter}>{getMessageIndex(baseInfo.timeseriesIndexList[0])}</h3></div>
+ </Col>
+ <Col span={6} style={{ height: "160px" }}>
+ <div className={styles.longStyle} hidden={baseInfo.timeseriesIndexList[1] ? false : true} onClick={() => doMessageShow("TimeseriesIndex", baseInfo.timeseriesIndexList[1] ? baseInfo.timeseriesIndexList[1].offset : undefined)}><h3 className={styles.hcenter}>{getMessageIndex(baseInfo.timeseriesIndexList[1])}</h3></div>
+ </Col>
+ <Col span={6} style={{ height: "160px" }}>
+ <div className={styles.longStyle} hidden={baseInfo.timeseriesIndexList[2] ? false : true} onClick={() => doMessageShow("TimeseriesIndex", baseInfo.timeseriesIndexList[2] ? baseInfo.timeseriesIndexList[2].offset : undefined)}><h3 className={styles.hcenter}>{getMessageIndex(baseInfo.timeseriesIndexList[2])}</h3></div>
+ </Col>
+ <Col span={6} style={{ height: "160px" }}>
+ <div className={styles.longStyle} onClick={() => doMessageShow("TIMORE")}><h3 className={styles.hcenter}>more infos</h3></div>
+ </Col>
+ </Row>
+ </div>
+
+ <div className={styles.rowEntry}>
+ <Row gutter={[8, 8]} align="middle" justify="center" style={{ height: "45px" }}>
+ <Col span={17} style={{ height: "40px" }}>
+ <div className={styles.shortStyle} onClick={() => doMessageShow("IndexOfTimeseriesIndex")}><h3 className={styles.hcenter}>TsfileMetaData[IndexOfTimeseriesIndex]</h3></div>
+ </Col>
+ <Col span={5} style={{ height: "40px" }}>
+ <div className={styles.shortStyle} onClick={() => doMessageShow("ITIMORE")}><h3 className={styles.hcenter}>more infos</h3></div>
+ </Col>
+ </Row>
+ </div>
+ {/* <div className={styles.row1}>
+ <Row align="middle" justify="center" style={{ height: "45px" }}>
+ <Col span={22} style={{ height: "40px" }}>
+ <div className={styles.col1} onClick={() => doMessageShow("TsfileMetaData")}><h3 className={styles.hcenter}>TsfileMetaData</h3></div>
+ </Col>
+ </Row>
+ </div> */}
+ <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>
+ </Col>
+ </Row>
+ </div>
+ <div className={styles.notoplinerow}>
+ <Row align="middle" justify="center" style={{ height: "45px" }}>
+ <Col span={22} style={{ height: "40px" }}>
+ <div className={styles.shortStyle} onClick={() => doMessageShow("TSFILE")}><h3 className={styles.hcenter}>TSFILE</h3></div>
+ </Col>
+ </Row>
+ </div>
+ </>
+ )
+}
+
+const ImageMessage = (props) => {
+ const { value, offset, showStructureContext, filePath } = props;
+ const [version, setVersion] = useState();
+ const [tsfileMetaDataSize, setTsfileMetaDataSize] = useState();
+
+ const getVersion = async () => {
+ let res = await getVersionUsingPOST({ filePath: filePath })
+ if (res.code == 0) {
+ setVersion(res.data.version)
+ } else {
+ notification.error({
+ message: res.message,
+ });
+ }
+ }
+
+ const getTsFileMetada = async () => {
+ let res = await getMetaDataSizeUsingPOST({ filePath: filePath })
+ if (res.code == 0) {
+ setTsfileMetaDataSize(res.data)
+ } else {
+ notification.error({
+ message: res.message,
+ });
+ }
+ }
+
+ 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';
+ return (<pre style={{ height: "55vh", overflow: "auto", whiteSpace: "pre-wrap" }}>{message}</pre>);
+ }
+
+ if (key == "ChunkGroup" && offset != undefined) {
+ return (
+ <div className={styles.detailRow}>
+ <div className={styles.chunkgroup} >
+ <Row style={{ height: "30vh" }}>
+ <Col span={3}><div className={styles.chunkgroupheader} onClick={() => { showStructureContext(key, 'CGH') }}>CGH</div></Col>
+ <Col span={16}>
+ <div className={styles.chunk1} >
+ <Row style={{ height: "25vh" }}>
+ <Col span={3}><div className={styles.chunkheader} onClick={() => { showStructureContext(key, 'CH') }}>CH</div></Col>
+ <Col span={20}>
+ <div className={styles.chunkdata} >
+ <Row style={{ height: "20vh" }}>
+ <Col span={4}><div className={styles.pageheader} onClick={() => { showStructureContext(key, 'PH') }}>PH</div></Col>
+ <Col span={4}><div className={styles.pagedata} onClick={() => { showStructureContext('', '') }}>PD</div></Col>
+ <Col span={4}><div className={styles.pageheader1} onClick={() => { showStructureContext('', '') }}>PH</div></Col>
+ <Col span={4}><div className={styles.pagedata1} onClick={() => { showStructureContext('', '') }}>PD</div></Col>
+ <Col span={4}><div className={styles.pageheader1} onClick={() => { showStructureContext('', '') }}>more infos</div></Col>
+ </Row>
+ </div>
+ </Col>
+ </Row>
+ </div>
+ </Col>
+ <Col span={3}><div className={styles.chunk} >more infos</div></Col>
+ </Row>
+ </div>
+ </div>
+ )
+ }
+
+ if (key != undefined && key.indexOf("MORE") > -1) {
+ return <div></div>
+ }
+
+ // || key == "TIMORE"
+ if (key == "TimeseriesIndex" && offset != undefined) {
+ return (
+ <div className={styles.detailRow}>
+ <div className={styles.chunkgroup} >
+ <Row style={{ height: "30vh" }}>
+ <Col span={3}><div className={styles.timeseriesmetadata} onClick={() => { showStructureContext(key, 'TM') }}>TM</div></Col>
+ <Col span={3}>
+ <div className={styles.chunkmetadata} onClick={() => { showStructureContext(key, 'CM') }}>
+ CM
+ </div>
+ </Col>
+ <Col span={3}><div className={styles.chunkmetadata1} onClick={() => { showStructureContext('', '') }}>CM</div></Col>
+ <Col span={3}><div className={styles.timeseriesmetadata1} onClick={() => { showStructureContext('', '') }}>TM</div></Col>
+ <Col span={3}>
+ <div className={styles.chunkmetadata1} onClick={() => { showStructureContext('', '') }}>
+ CM
+ </div>
+ </Col>
+ <Col span={3}><div className={styles.chunkmetadata1} onClick={() => { showStructureContext('', '') }}>CM</div></Col>
+ <Col span={3}><div className={styles.timeseriesmetadata1} onClick={() => { showStructureContext('', '') }}>more infos</div></Col>
+
+ </Row>
+ </div>
+ </div>
+ )
+ }
+
+ // || key == "ITIMORE"
+ if (key == "IndexOfTimeseriesIndex") {
+ return (
+ <div className={styles.detailRow}>
+ <div className={styles.chunkgroup} >
+ <Row style={{ height: "30vh" }}>
+ <Col span={17}>
+ <div className={styles.metadataIndexNode}>
+ <Row style={{ height: "25vh" }}>
+ <Col span={5}><div className={styles.childSize} onClick={() => { showStructureContext(key, 'childSize') }}>childSize</div></Col>
+ <Col span={5}><div className={styles.list} onClick={() => { showStructureContext(key, 'list') }}>{'List<ITI>'}</div></Col>
+ <Col span={5}><div className={styles.childSize} onClick={() => { showStructureContext(key, 'offset') }}>offset</div></Col>
+ <Col span={5}><div className={styles.list} onClick={() => { showStructureContext(key, 'NodeType') }}>NodeType</div></Col>
+ </Row>
+ </div>
+ </Col>
+ <Col span={3}>
+ <div className={styles.metaOffset} onClick={() => { showStructureContext(key, 'metaOffset') }}>metaOffset</div>
+ </Col>
+ <Col span={3}>
+ <div className={styles.bloomFilter} onClick={() => { showStructureContext('', '') }}>bloomFilter</div>
+ </Col>
+ </Row>
+ </div>
+ </div>
+ )
+ }
+
+ // if (key == "TsfileMetaData") { TsfileMetaData 中 包含IndexoftimeseriesIndex信息
+ // // return (<Image align="center" width="100%" height="100%" src={tsfilemetadata}></Image>);
+ // }
+
+ if (key == "TsfileMetaDataSize") {
+ getTsFileMetada()
+ return (<pre style={{ height: "55vh", overflow: "auto", whiteSpace: "pre-wrap" }}>TsfileMetaDataSize:{tsfileMetaDataSize}</pre>);
+ }
+ }
+
+ return (
+ <div style={{ width: "100%", height: "100%" }}>
+ {showImage(value, offset)}
+ </div>
+ )
+}
+
+
+const Overview = (props) => {
+ const [clickArea, setClickArea] = useState()
+ const [clickAreaOffset, setClickAreaOffset] = useState(undefined)
+ const [popShow, setPpoShow] = useState(false)
+ const [chunkGroupBrief, setChunkGroupBrief] = useState()
+ const [timeseriesIndexBrief, setTimeseriesIndexBrief] = useState()
+ const [indexTimeseriesIndexBrief, setIndexTimeseriesIndexBrief] = useState()
+ // 结构CGH/CH/PH点击所对应的内容
+ const [structureContext, setStructureContext] = useState()
+
+ const { fileName, filePath, baseInfo } = props;
+
+ const doChange = (structureName, offset) => {
+ setClickArea(structureName)
+ setClickAreaOffset(offset)
+ if (structureName.indexOf("MORE") > -1) {
+ setStructureContext();
+ setPpoShow({ flag: true, structureName: structureName, fileName: fileName, filePath: filePath })
+ }
+
+ if (structureName == 'ChunkGroup') {
+ if (offset != undefined) {
+ getCGBriefInfo(offset)
+ } else {
+ setStructureContext();
+ }
+ }
+
+ if (structureName == 'TimeseriesIndex') {
+ if (offset != undefined) {
+ getTIBriefInfo(offset)
+ } else {
+ setStructureContext();
+ }
+ }
+
+ if (structureName == 'IndexOfTimeseriesIndex') {
+ getITIBriefInfo()
+ }
+ }
+
+ const getCGBriefInfo = async (offset) => {
+ let res = await getChunkGroupInfoUsingPOST({ offset: offset, filePath: filePath })
+ if (res.code == 0) {
+ setChunkGroupBrief(res.data)
+ } else {
+ notification.error({
+ message: res.message,
+ });
+ }
+ }
+
+ const getTIBriefInfo = async (offset) => {
+ let res = await getTimeseriesIndexInfoUsingPOST({ offset: offset, filePath: filePath })
+ if (res.code == 0) {
+ setTimeseriesIndexBrief(res.data)
+ } else {
+ notification.error({
+ message: res.message,
+ });
+ }
+ }
+
+ const getITIBriefInfo = async () => {
+ let res = await getMetaDataUsingPOST({ filePath: filePath })
+ if (res.code == 0) {
+ setIndexTimeseriesIndexBrief(res.data)
+ } else {
+ notification.error({
+ message: res.message,
+ });
+ }
+
+ }
+
+ // flag1 代表chunkgroup、timeseriesIndex、等,flag2代表flag1下的二级标志
+ const showStructureContext = (level1Flag, level2Flag) => {
+ if (level1Flag == 'ChunkGroup') {
+ if (level2Flag == 'CGH') {
+ let info = "结构说明:\n"
+ + "\t CGH = ChunkGroupHeader \n"
+ + "\t CGD = ChunkGroupData \n"
+ + "\t CGD = n * Chunk \n"
+ + "\t ChunkGroup = CGH +CGD \n"
+ + "内容详情: \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"
+ + "\t CH = ChunkHeader \n"
+ + "\t CD = ChunkData \n"
+ + "\t CD = n * Page \n"
+ + "\t Chunk = CH +CD \n"
+ + "内容详情: \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"
+ + "\t PH = PageHeader \n"
+ + "\t PD = PageData \n"
+ + "\t Page = PH + PD \n"
+ + "内容详情: \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"
+ + "\t TM = TimeseriesMetadata \n"
+ + "\t TimeseriesIndex = TM + n*CM \n"
+ + "内容详情: \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"
+ + "\t CM = ChunkMetadata \n"
+ + "内容详情: \n"
+ setStructureContext(<pre style={{ height: "55vh", overflow: "auto", whiteSpace: "pre-wrap" }}>{info}{JSON.stringify(timeseriesIndexBrief.cm, null, '\t')}</pre>)
+ }
+ } else if (level1Flag == 'IndexOfTimeseriesIndex') {
+ if (level2Flag == 'childSize') {
+ let info = " \n childSize:"
+ setStructureContext(<pre style={{ height: "55vh", overflow: "auto", whiteSpace: "pre-wrap" }}>{info}{indexTimeseriesIndexBrief.metadataIndexNodeVo.childSize}</pre>)
+ }
+ if (level2Flag == 'list') {
+ let info = " \n List<ITI>:"
+ setStructureContext(<pre style={{ height: "55vh", overflow: "auto", whiteSpace: "pre-wrap" }}>{info}{JSON.stringify(indexTimeseriesIndexBrief.metadataIndexNodeVo.metadataIndexEntryList, null, '\t')}</pre>)
+ }
+ if (level2Flag == 'offset') {
+ let info = "\n offset:"
+ setStructureContext(<pre style={{ height: "55vh", overflow: "auto", whiteSpace: "pre-wrap" }}>{info}{indexTimeseriesIndexBrief.metadataIndexNodeVo.offset}</pre>)
+ }
+ if (level2Flag == 'NodeType') {
+ let info = "\n NodeType:"
+ setStructureContext(<pre style={{ height: "55vh", overflow: "auto", whiteSpace: "pre-wrap" }}>{info}{indexTimeseriesIndexBrief.metadataIndexNodeVo.nodeType}</pre>)
+ }
+ if (level2Flag == 'metaOffset') {
+ let info = "\n metaOffset:"
+ setStructureContext(<pre style={{ height: "55vh", overflow: "auto", whiteSpace: "pre-wrap" }}>{info}{indexTimeseriesIndexBrief.metaOffset}</pre>)
+ }
+ } else {
+ setStructureContext()
+ }
+ }
+
+ const closePop = () => {
+ setPpoShow({ flag: false })
+ }
+
+ return (
+ <>
+ <Layout>
+ <Sider width="50%" style={{ height: "100vh", background: "white", paddingTop: 60, paddingLeft: 15, paddingRight: 15 }}>
+ <Tsfile doChange={doChange} showStructureContext={showStructureContext} fileName={fileName} baseInfo={baseInfo}></Tsfile>
+ </Sider>
+ <Layout style={{ background: "white", paddingTop: 60, paddingLeft: 15, paddingRight: 15 }}>
+ <Content style={{ height: "33vh", background: "white" }}>
+ <ImageMessage value={clickArea} offset={clickAreaOffset} showStructureContext={showStructureContext} filePath={filePath}></ImageMessage>
+ </Content>
+ <Content style={{ height: "60vh", background: "white" }}>
+ {structureContext}
+ </Content>
+ <PopDetails popShow={popShow} closePop={() => closePop()}></PopDetails>
+ </Layout>
+ </Layout>
+
+ </>
+ )
+}
+
+export default Overview
\ No newline at end of file
diff --git a/tsfile-viewer-web-frontend/src/pages/tsfile-tool/v2/overview/popDetails.jsx b/tsfile-viewer-web-frontend/src/pages/tsfile-tool/v2/overview/popDetails.jsx
new file mode 100644
index 0000000..58d93e3
--- /dev/null
+++ b/tsfile-viewer-web-frontend/src/pages/tsfile-tool/v2/overview/popDetails.jsx
@@ -0,0 +1,107 @@
+/*
+ * 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.
+ */
+import { Modal,Tooltip } from 'antd';
+import { QuestionCircleOutlined } from '@ant-design/icons';
+import React, { useState } from 'react';
+import MoreChunkGroup from './moreChunkGroup';
+import MoreIndexOfTimeseriesIndex from './moreIndexOfTimeseriesIndex';
+import MoreTimeseriesIndex from './moreTimeseriesIndex';
+import { useIntl } from 'umi';
+
+const PopDetails = (props) => {
+
+ const [cardList, setCardList] = useState()
+ const intl = useIntl();
+
+ const doOnCancel = () => {
+ props.closePop();
+ setCardList()
+ }
+
+ const showtitle = (key) => {
+ if (key == "ITIMORE") {
+ return <>
+ <Tooltip placement="bottom" title={<span>
+ {intl.formatMessage({ id: 'tsviewer.popDetail.indexOfTimeseriesIndex.explanation', })}<br />
+ </span>}>
+ <QuestionCircleOutlined />
+ </Tooltip>
+ <span>
+ {"\u00A0\u00A0 IndexOfTimeseriesIndex"}
+ </span>
+ </>
+ }
+ if (key == "CGMORE") {
+ return <>
+ <Tooltip placement="bottom" title={<span>
+ {intl.formatMessage({ id: 'tsviewer.popDetail.chunkgroup.explanation', })}<br />
+ {intl.formatMessage({ id: 'tsviewer.popDetail.chunkgroup.explanation1', })}<br />
+ </span>}>
+ <QuestionCircleOutlined />
+ </Tooltip>
+ <span>
+ {"\u00A0\u00A0 ChunkGroup"}
+ </span>
+ </>
+ }
+ if (key == "TIMORE") {
+ return <>
+ <Tooltip placement="bottom" title={<span>
+ {intl.formatMessage({ id: 'tsviewer.popDetail.timeseriesIndex.explanation', })}<br />
+ {intl.formatMessage({ id: 'tsviewer.popDetail.timeseriesIndex.explanation1', })}<br />
+ </span>}>
+ <QuestionCircleOutlined />
+ </Tooltip>
+ <span>
+ {"\u00A0\u00A0 TimeseriesIndex"}
+ </span>
+ </>
+ }
+ }
+
+ const getBlock = (key) => {
+ if (key == "ITIMORE") {
+ return <MoreIndexOfTimeseriesIndex fileName={props.popShow.fileName} filePath={props.popShow.filePath}></MoreIndexOfTimeseriesIndex>;
+ }
+ if (key == "CGMORE") {
+ return <MoreChunkGroup fileName={props.popShow.fileName} filePath={props.popShow.filePath} cardList={cardList} setCardList={setCardList}></MoreChunkGroup>;
+ }
+ if (key == "TIMORE") {
+ return <MoreTimeseriesIndex fileName={props.popShow.fileName} filePath={props.popShow.filePath} cardList={cardList} setCardList={setCardList}></MoreTimeseriesIndex>;
+ }
+ }
+
+
+ return (
+ <div>
+ <Modal
+ title={showtitle(props.popShow.structureName)}
+ centered
+ visible={props.popShow.flag}
+ onCancel={() => doOnCancel()}
+ maskClosable={false}
+ destroyOnClose={true}
+ width={"80%"}
+ footer={[]}
+ >
+ {getBlock(props.popShow.structureName)}
+ </Modal>
+ </div >
+ );
+}
+
+export default PopDetails
\ No newline at end of file
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
new file mode 100644
index 0000000..b96fe60
--- /dev/null
+++ b/tsfile-viewer-web-frontend/src/pages/tsfile-tool/v2/searchData/searchPageData.jsx
@@ -0,0 +1,153 @@
+/*
+ * 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.
+ */
+import React, { useState } from "react";
+import { PageHeader, DatePicker, Input, Table, notification, message } from "antd";
+import {
+ fetchDataByDeviceAndMeasurementUsingPOST
+} from '@/services/swagger1/tsfileViewerController'
+import moment from 'moment';
+import { useIntl } from 'umi';
+
+const { Search } = Input;
+
+
+const SearchPageData = (props) => {
+
+ const [columns, setColumns] = useState();
+ const [pageData, setPageData] = useState()
+
+ const [beginDate, setBeginDate] = useState()
+ const [endDate, setEndDate] = useState();
+ const [device, setDevice] = useState()
+ const [measurement, setMeasureMent] = useState()
+ const { filePath } = props;
+ const intl = useIntl();
+
+
+ const doQuery = async () => {
+
+
+ if (device == undefined || device == null || device == '') {
+ notification.info({ message: "device " + intl.formatMessage({ id: 'tsviewer.more.notNull', }) })
+ return;
+ }
+
+ if (measurement == undefined || measurement == null || measurement == '') {
+ 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', }))
+ return;
+ }
+
+ if (endDate == undefined || endDate == null || endDate == '') {
+ notification.info({ message: "device " + intl.formatMessage({ id: 'tsviewer.more.notNull', }) })
+ return;
+ }
+
+ let param = {};
+ param['filePath'] = filePath;
+ param['device'] = device;
+ param['measurement'] = measurement;
+ param['beginDate'] = beginDate;
+ param['endDate'] = endDate;
+ let res = await fetchDataByDeviceAndMeasurementUsingPOST(param);
+ if (res.code == 0) {
+ setColumns(Object.values(res.data.title).map((titleName, key) => {
+ return {
+ title: titleName,
+ dataIndex: titleName,
+ key: titleName,
+ render: (text, record, index) => {
+ if (titleName == 'timestamp') {
+ return moment(Number(record[key])).format('YYYY-MM-DD HH:mm:ss.SSS')
+ }
+ return record[key]
+ }
+ }
+ }))
+ setPageData(res.data.values);
+ } else {
+ notification.error({
+ message: res.message,
+ });
+ }
+ }
+
+
+ return (
+ <>
+ <PageHeader
+ style={{ background: "white" }}
+ extra={(
+ <>
+ <DatePicker
+ format='YYYY-MM-DD HH:mm:ss'
+ placeholder={intl.formatMessage({ id: 'tsviewer.more.beginDate', })}
+ showTime={{ format: 'HH:mm:ss' }}
+ onChange={(date) => {
+ if (date != null) {
+ date = date.set({millisecond: 0 })
+ }
+ setBeginDate(isNaN(moment(date).valueOf()) ? '' : moment(date).valueOf())
+ }}
+ />
+ <DatePicker
+ format='YYYY-MM-DD HH:mm:ss'
+ placeholder={intl.formatMessage({ id: 'tsviewer.more.endDate', })}
+ showTime={{ format: 'HH:mm:ss' }}
+ onChange={(date) => {
+ if (date != null) {
+ date = date.set({millisecond: 0 })
+ }
+ setEndDate(isNaN(moment(date).valueOf()) ? '' : moment(date).valueOf())
+ }}
+ />
+ <Input
+ placeholder="device"
+ allowClear
+ onChange={(e) => {
+ setDevice(e.target.value)
+ }}
+ style={{
+ width: 400,
+ }} />
+ <Search
+ placeholder="measurement"
+ allowClear
+ onChange={(e) => {
+ setMeasureMent(e.target.value)
+ }}
+ onSearch={() => doQuery()}
+ style={{
+ width: 200,
+ }}
+ />
+ </>
+ )}>
+ </PageHeader>
+ <Table columns={columns} dataSource={pageData}
+ pagination={{ defaultPageSize: 13, showQuickJumper: true, position: ["bottomCenter"] }}
+ bordered />
+ </>
+
+ )
+}
+
+export default SearchPageData
\ No newline at end of file
diff --git a/tsfile-viewer-web-frontend/src/pages/tsfile-tool/v2/style.less b/tsfile-viewer-web-frontend/src/pages/tsfile-tool/v2/style.less
new file mode 100644
index 0000000..27cdad6
--- /dev/null
+++ b/tsfile-viewer-web-frontend/src/pages/tsfile-tool/v2/style.less
@@ -0,0 +1,255 @@
+/*
+ * 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.
+ */
+.row{
+ background: #cccccc;
+ border: 2px solid black;
+ width: 100%;
+ padding: 5;
+}
+
+.notoplinerow{
+ background: #cccccc;
+ border-left: 2px solid black;
+ border-right: 2px solid black;
+ border-bottom: 2px solid black;
+ width: 100%;
+ padding: 5;
+}
+
+.rowEntry{
+ background: #fd6969;
+ border-left: 2px solid black;
+ border-right: 2px solid black;
+ border-bottom: 2px solid black;
+ width: 100%;
+ padding: 5;
+}
+
+.longStyle{
+ background: #f2f2f2;
+ border: 2px solid black;
+ width: 100%;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ text-align: justify;
+ overflow: hidden;
+ cursor: pointer;
+}
+
+.shortStyle{
+ background: #f2f2f2;
+ border: 2px solid black;
+ width: 100%;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ text-align: justify;
+ cursor: pointer;
+}
+
+.colentry{
+ background: #ef4a4a;
+ border: 2px solid black;
+ width: 100%;
+ height: 100%;
+}
+
+.hcenter{
+ white-space: break-spaces;
+ overflow: hidden;
+ text-align: center;
+ width: 100%;
+ text-overflow: ellipsis;
+}
+
+.sitedrawerrenderincurrentwrapper {
+ position: relative;
+ height: 70vh;
+ overflow: hidden;
+
+ }
+
+.detailRow{
+ background: #b4b4b4;
+ //border: 2px solid black;
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+}
+
+.chunkgroupheader{
+ background: #f88989;
+ border: 2px solid black;
+ width: 95%;
+ height: 90%;
+ margin: 20px 20px 20px 20px;
+ cursor: pointer;
+}
+
+.chunkheader{
+ background: #e09cf6;
+ border: 2px solid black;
+ width: 95%;
+ height: 90%;
+ margin: 20px 20px 20px 20px;
+ cursor: pointer;
+}
+
+.chunkdata{
+ background: #3d3d3b;
+ border: 2px solid black;
+ width: 95%;
+ height: 90%;
+ margin: 20px 20px 20px 20px;
+}
+
+.pageheader{
+ background: #509f70;
+ border: 2px solid black;
+ width: 95%;
+ height: 90%;
+ margin: 20px 20px 20px 20px;
+ cursor: pointer;
+}
+
+.pageheader1{
+ background: #509f70;
+ border: 2px solid black;
+ width: 95%;
+ height: 90%;
+ margin: 20px 20px 20px 20px;
+}
+
+.pagedata{
+ background: #67a3b6;
+ border: 2px solid black;
+ width: 95%;
+ height: 90%;
+ margin: 20px 20px 20px 20px;
+}
+
+.pagedata1{
+ background: #67a3b6;
+ border: 2px solid black;
+ width: 95%;
+ height: 90%;
+ margin: 20px 20px 20px 20px;
+}
+
+.chunkgroup{
+ background: #ffffff;
+ border: 2px solid black;
+ width: 95%;
+ height: 90%;
+ margin: 20px 20px 20px 20px;
+}
+
+.chunk{
+ background: #d4da8d;
+ border: 2px solid black;
+ width: 100%;
+ height: 90%;
+ margin: 20px 20px 20px 0px;
+}
+
+.chunk1{
+ background: #d4da8d;
+ border: 2px solid black;
+ width: 100%;
+ height: 90%;
+ margin: 20px 0px 20px 20px;
+}
+
+.timeseriesmetadata{
+ background: #a88e8e;
+ border: 2px solid black;
+ width: 100%;
+ height: 90%;
+ margin: 20px 0px 20px 20px;
+ cursor: pointer;
+}
+
+.timeseriesmetadata1{
+ background: #a88e8e;
+ border: 2px solid black;
+ width: 100%;
+ height: 90%;
+ margin: 20px 0px 20px 0px;
+}
+
+.chunkmetadata{
+ background: #d4da8d;
+ border: 2px solid black;
+ width: 100%;
+ height: 90%;
+ margin: 20px 0px 20px 0px;
+ cursor: pointer;
+}
+
+.chunkmetadata1{
+ background: #d4da8d;
+ border: 2px solid black;
+ width: 100%;
+ height: 90%;
+ margin: 20px 0px 20px 0px;
+}
+
+.metadataIndexNode{
+ background: #b4b9db;
+ border: 2px solid black;
+ width: 100%;
+ height: 90%;
+ margin: 20px 0px 20px 20px;
+}
+
+.metaOffset{
+ background: #5d938d;
+ border: 2px solid black;
+ width: 100%;
+ height: 90%;
+ margin: 20px 0px 20px 0px;
+ cursor: pointer;
+}
+
+.bloomFilter{
+ background: #c4c89f;
+ border: 2px solid black;
+ width: 100%;
+ height: 90%;
+ margin: 20px 0px 20px 0px;
+}
+
+.childSize{
+ background: #509f70;
+ border: 2px solid black;
+ width: 95%;
+ height: 90%;
+ margin: 20px 20px 20px 20px;
+ cursor: pointer;
+}
+
+.list{
+ background: #67a3b6;
+ border: 2px solid black;
+ width: 95%;
+ height: 90%;
+ margin: 20px 20px 20px 20px;
+ cursor: pointer;
+}
\ No newline at end of file
diff --git a/tsfile-viewer-web-frontend/src/service-worker.js b/tsfile-viewer-web-frontend/src/service-worker.js
new file mode 100644
index 0000000..5fb8fc1
--- /dev/null
+++ b/tsfile-viewer-web-frontend/src/service-worker.js
@@ -0,0 +1,82 @@
+/*
+ * 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.
+ */
+/* eslint-disable eslint-comments/disable-enable-pair */
+/* eslint-disable no-restricted-globals */
+/* eslint-disable no-underscore-dangle */
+/* globals workbox */
+workbox.core.setCacheNameDetails({
+ prefix: 'antd-pro',
+ suffix: 'v5',
+});
+// Control all opened tabs ASAP
+workbox.clientsClaim();
+
+/**
+ * Use precaching list generated by workbox in build process.
+ * https://developers.google.com/web/tools/workbox/reference-docs/latest/workbox.precaching
+ */
+workbox.precaching.precacheAndRoute(self.__precacheManifest || []);
+
+/**
+ * Register a navigation route.
+ * https://developers.google.com/web/tools/workbox/modules/workbox-routing#how_to_register_a_navigation_route
+ */
+workbox.routing.registerNavigationRoute('/index.html');
+
+/**
+ * Use runtime cache:
+ * https://developers.google.com/web/tools/workbox/reference-docs/latest/workbox.routing#.registerRoute
+ *
+ * Workbox provides all common caching strategies including CacheFirst, NetworkFirst etc.
+ * https://developers.google.com/web/tools/workbox/reference-docs/latest/workbox.strategies
+ */
+
+/** Handle API requests */
+workbox.routing.registerRoute(/\/api\//, workbox.strategies.networkFirst());
+
+/** Handle third party requests */
+workbox.routing.registerRoute(
+ /^https:\/\/gw\.alipayobjects\.com\//,
+ workbox.strategies.networkFirst(),
+);
+workbox.routing.registerRoute(
+ /^https:\/\/cdnjs\.cloudflare\.com\//,
+ workbox.strategies.networkFirst(),
+);
+workbox.routing.registerRoute(/\/color.less/, workbox.strategies.networkFirst());
+
+/** Response to client after skipping waiting with MessageChannel */
+addEventListener('message', (event) => {
+ const replyPort = event.ports[0];
+ const message = event.data;
+ if (replyPort && message && message.type === 'skip-waiting') {
+ event.waitUntil(
+ self.skipWaiting().then(
+ () => {
+ replyPort.postMessage({
+ error: null,
+ });
+ },
+ (error) => {
+ replyPort.postMessage({
+ error,
+ });
+ },
+ ),
+ );
+ }
+});
diff --git a/tsfile-viewer-web-frontend/src/services/swagger1/index.ts b/tsfile-viewer-web-frontend/src/services/swagger1/index.ts
new file mode 100644
index 0000000..a5038dd
--- /dev/null
+++ b/tsfile-viewer-web-frontend/src/services/swagger1/index.ts
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+// @ts-ignore
+/* eslint-disable */
+// API 更新时间:
+// API 唯一标识:
+import * as tsfileViewerController from './tsfileViewerController';
+export default {
+ tsfileViewerController,
+};
diff --git a/tsfile-viewer-web-frontend/src/services/swagger1/tsfileViewerController.ts b/tsfile-viewer-web-frontend/src/services/swagger1/tsfileViewerController.ts
new file mode 100644
index 0000000..ca2fadd
--- /dev/null
+++ b/tsfile-viewer-web-frontend/src/services/swagger1/tsfileViewerController.ts
... 5648 lines suppressed ...