├── .gitattributes
├── .gitignore
├── .travis.yml
├── Dockerfile
├── LICENSE
├── README.md
├── build.bat
├── build.sh
├── build_dev.bat
├── build_dev.sh
├── build_release
├── build_release.sh
├── start.bat
└── start.sh
├── config
├── poli.docker.properties
└── poli.properties
├── db
├── poli.db
├── schema-postgresql.sql
└── schema-sqlite.sql
├── docs
├── .nojekyll
├── _coverpage.md
├── _images
│ └── screenshots
│ │ ├── apikey.jpg
│ │ ├── apikey2.jpg
│ │ ├── autorefresh.jpg
│ │ ├── canned_report.jpg
│ │ ├── component_card.jpg
│ │ ├── component_funnel.jpg
│ │ ├── component_heatmap.jpg
│ │ ├── component_treemap.jpg
│ │ ├── date_picker.jpg
│ │ ├── drillthrough1.jpg
│ │ ├── drillthrough2.jpg
│ │ ├── drillthrough3.jpg
│ │ ├── drillthrough4.jpg
│ │ ├── ds1.jpg
│ │ ├── ds2.jpg
│ │ ├── export_csv.jpg
│ │ ├── export_to_pdf_button.jpg
│ │ ├── export_to_pdf_dialog.jpg
│ │ ├── favourite.jpg
│ │ ├── fullscreen1.jpg
│ │ ├── fullscreen2.jpg
│ │ ├── group1.jpg
│ │ ├── group2.jpg
│ │ ├── login.jpg
│ │ ├── login2.jpg
│ │ ├── login3.jpg
│ │ ├── more_options_button.jpg
│ │ ├── report1.jpg
│ │ ├── report10.jpg
│ │ ├── report11.jpg
│ │ ├── report12.jpg
│ │ ├── report13.jpg
│ │ ├── report14.jpg
│ │ ├── report2.jpg
│ │ ├── report3.jpg
│ │ ├── report4.jpg
│ │ ├── report4_1.jpg
│ │ ├── report5.jpg
│ │ ├── report6.jpg
│ │ ├── report7.jpg
│ │ ├── report8.jpg
│ │ ├── report9.jpg
│ │ ├── rls_table.jpg
│ │ ├── rls_user_attributes.jpg
│ │ ├── share_button.jpg
│ │ ├── share_dialog.jpg
│ │ ├── share_event.jpg
│ │ ├── slicer.jpg
│ │ ├── style1.jpg
│ │ ├── style2.jpg
│ │ ├── user1.jpg
│ │ ├── user2.jpg
│ │ ├── user3.jpg
│ │ └── user4.jpg
├── _media
│ ├── favicon.ico
│ └── style.css
├── _sidebar.md
├── build.md
├── change-logs.md
├── configuration.md
├── data-source.md
├── index.html
├── installation.md
├── quick-start.md
├── report-component.md
├── report.md
├── upgrade.md
└── user-management.md
├── export-server
├── .gitignore
├── package-lock.json
├── package.json
└── poli-export-server.js
├── pom.xml
├── src
├── main
│ ├── java
│ │ └── com
│ │ │ └── shzlw
│ │ │ └── poli
│ │ │ ├── App.java
│ │ │ ├── AppStartUpListener.java
│ │ │ ├── config
│ │ │ ├── AppConfig.java
│ │ │ └── AppProperties.java
│ │ │ ├── dao
│ │ │ ├── AuditLogDao.java
│ │ │ ├── CannedReportDao.java
│ │ │ ├── ComponentDao.java
│ │ │ ├── DaoHelper.java
│ │ │ ├── GroupDao.java
│ │ │ ├── JdbcDataSourceDao.java
│ │ │ ├── ReportDao.java
│ │ │ ├── SavedQueryDao.java
│ │ │ ├── SharedReportDao.java
│ │ │ ├── UserDao.java
│ │ │ └── UserFavouriteDao.java
│ │ │ ├── dto
│ │ │ ├── AppInfo.java
│ │ │ ├── Column.java
│ │ │ ├── ExportRequest.java
│ │ │ ├── FilterParameter.java
│ │ │ ├── LoginResponse.java
│ │ │ ├── QueryRequest.java
│ │ │ ├── QueryResult.java
│ │ │ ├── SharedLinkInfo.java
│ │ │ ├── SharedReportRow.java
│ │ │ └── Table.java
│ │ │ ├── filter
│ │ │ ├── AuthFilter.java
│ │ │ └── AuthFilterHelper.java
│ │ │ ├── model
│ │ │ ├── AuditLog.java
│ │ │ ├── CannedReport.java
│ │ │ ├── Component.java
│ │ │ ├── Group.java
│ │ │ ├── JdbcDataSource.java
│ │ │ ├── Report.java
│ │ │ ├── SavedQuery.java
│ │ │ ├── SharedReport.java
│ │ │ ├── User.java
│ │ │ └── UserAttribute.java
│ │ │ ├── rest
│ │ │ ├── AuditLogWs.java
│ │ │ ├── AuthWs.java
│ │ │ ├── CannedReportWs.java
│ │ │ ├── ComponentWs.java
│ │ │ ├── GroupWs.java
│ │ │ ├── InfoWs.java
│ │ │ ├── JdbcDataSourceWs.java
│ │ │ ├── JdbcQueryWs.java
│ │ │ ├── ReportWs.java
│ │ │ ├── SavedQueryWs.java
│ │ │ ├── SharedReportWs.java
│ │ │ ├── UserWs.java
│ │ │ └── api
│ │ │ │ └── SavedQueryEndpointWs.java
│ │ │ ├── service
│ │ │ ├── AuditLogService.java
│ │ │ ├── HttpClient.java
│ │ │ ├── JdbcDataSourceService.java
│ │ │ ├── JdbcQueryService.java
│ │ │ ├── JdbcQueryServiceHelper.java
│ │ │ ├── ReportService.java
│ │ │ ├── SharedReportService.java
│ │ │ └── UserService.java
│ │ │ └── util
│ │ │ ├── CommonUtils.java
│ │ │ ├── Constants.java
│ │ │ ├── HttpUtils.java
│ │ │ ├── PasswordUtils.java
│ │ │ └── RawStringDeserialzier.java
│ └── resources
│ │ ├── application.properties
│ │ ├── loader.properties
│ │ └── logback-spring.xml
└── test
│ ├── java
│ └── com
│ │ └── shzlw
│ │ └── poli
│ │ ├── dao
│ │ ├── AuditLogDaoTest.java
│ │ └── DaoHelperTest.java
│ │ ├── filter
│ │ └── AuthFilterTest.java
│ │ ├── rest
│ │ ├── AbstractWsTest.java
│ │ ├── AuthWsTest.java
│ │ ├── CannedReportWsTest.java
│ │ ├── ComponentWsTest.java
│ │ ├── GroupWsTest.java
│ │ ├── InfoWsTest.java
│ │ ├── JdbcDataSourceWsTest.java
│ │ ├── JdbcQueryWsTest.java
│ │ ├── ReportWsTest.java
│ │ ├── SavedQueryWsTest.java
│ │ ├── SharedReportWsTest.java
│ │ └── UserWsTest.java
│ │ ├── service
│ │ └── JdbcQueryServiceHelperTest.java
│ │ └── util
│ │ ├── CommonUtilsTest.java
│ │ └── PasswordUtilsTest.java
│ └── resources
│ ├── application-test.properties
│ ├── logback-test.xml
│ └── schema-sqlite.sql
├── third-party-license
├── license-HikariCP
├── license-SQLite JDBC
├── license-axios
├── license-echarts
├── license-echarts-for-react
├── license-fontawesome
├── license-guava
├── license-okhttp
├── license-puppeteer
├── license-react
├── license-react-ace
├── license-react-beautiful-dnd
├── license-react-color
├── license-react-i18next
├── license-react-router
├── license-react-select
├── license-react-table
├── license-react-toastify
└── license-spring boot
├── upgrade
├── poli_upgrade_v0.10.0.sql
├── poli_upgrade_v0.12.0.sql
├── poli_upgrade_v0.7.0.sql
└── poli_upgrade_v0.9.0.sql
├── version
└── web-app
├── .gitignore
├── package-lock.json
├── package.json
├── public
├── favicon.ico
├── index.html
└── manifest.json
└── src
├── App.css
├── App.js
├── App.test.js
├── api
├── ApiService.js
├── Constants.js
├── EchartsApi.js
└── Util.js
├── components
├── Checkbox
│ ├── Checkbox.css
│ └── Checkbox.js
├── ColorPicker
│ ├── ColorPicker.css
│ └── ColorPicker.js
├── DropdownDialog
│ ├── DropdownDialog.css
│ └── DropdownDialog.js
├── GridDraggable.js
├── GridItem.js
├── GridLayout.css
├── GridLayout.js
├── GridResizable.js
├── Kanban
│ ├── Kanban.css
│ └── Kanban.js
├── Modal
│ ├── Modal.css
│ └── Modal.js
├── SearchInput
│ ├── SearchInput.css
│ └── SearchInput.js
├── Select.js
├── SelectButtons
│ ├── SelectButtons.css
│ └── SelectButtons.js
├── Tabs
│ ├── Tabs.css
│ └── Tabs.js
├── Toast
│ ├── Toast.css
│ └── Toast.js
├── filters
│ ├── DatePicker.css
│ ├── DatePicker.js
│ ├── InputRange.css
│ ├── InputRange.js
│ ├── Paginator.css
│ ├── Paginator.js
│ ├── Slicer.css
│ └── Slicer.js
├── processbar
│ ├── Processbar.css
│ └── Processbar.js
├── table
│ ├── Table.css
│ └── Table.js
└── widgets
│ ├── Card.css
│ ├── Card.js
│ ├── Iframe.js
│ ├── ImageBox.css
│ ├── ImageBox.js
│ ├── InnerHtml.js
│ ├── TextBox.css
│ └── TextBox.js
├── i18n.js
├── index.css
├── index.js
├── locales
├── en
│ └── translation.json
├── es
│ └── translation.json
├── fr
│ └── translation.json
└── zh
│ └── translation.json
└── views
├── Account.js
├── DataSource.js
├── Event
├── AuditLog.js
└── SharedReportView.js
├── Login
├── ChangeTempPassword.js
├── Login.css
└── Login.js
├── PageNotFound.js
├── Report
├── ComponentEditPanel.css
├── ComponentEditPanel.js
├── ComponentViewPanel.css
├── ComponentViewPanel.js
├── Report.css
├── Report.js
└── ReportEditView.js
├── ReportFullScreenView.js
├── Studio
├── ReactSelectHelper.js
├── SchemaPanel.css
├── SchemaPanel.js
├── ScrollTabPanel.css
├── ScrollTabPanel.js
├── Studio.css
└── Studio.js
├── UserManagement
├── Group.js
└── User.js
├── Workspace.css
└── Workspace.js
/.gitattributes:
--------------------------------------------------------------------------------
1 | * linguist-vendored
2 | *.java linguist-vendored=false
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | *.iml
3 | target/
4 | .settings/org.eclipse.jdt.core.prefs
5 | .classpath
6 | .project
7 | log/
8 | jdbc-drivers/*
9 |
10 | # Ignore the files copied from web-app build
11 | src/main/resources/static
12 |
13 | release/
14 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: java
2 | jdk:
3 | - openjdk8
4 |
5 | after_success:
6 | - bash <(curl -s https://codecov.io/bash)
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # Build
2 | FROM openjdk:8-jdk-alpine as builder
3 |
4 | WORKDIR /app/src
5 | COPY . .
6 |
7 | # Install node and npm
8 | RUN apk add --update nodejs nodejs-npm
9 |
10 | ENV MAVEN_VERSION 3.5.4
11 | ENV MAVEN_HOME /usr/lib/mvn
12 | ENV PATH $MAVEN_HOME/bin:$PATH
13 |
14 | # Install maven
15 | RUN wget http://archive.apache.org/dist/maven/maven-3/$MAVEN_VERSION/binaries/apache-maven-$MAVEN_VERSION-bin.tar.gz && \
16 | tar -zxvf apache-maven-$MAVEN_VERSION-bin.tar.gz && \
17 | rm apache-maven-$MAVEN_VERSION-bin.tar.gz && \
18 | mv apache-maven-$MAVEN_VERSION /usr/lib/mvn
19 |
20 | # Build the jar
21 | RUN ./build.sh
22 |
23 | # Deploy
24 | FROM openjdk:8-jre-alpine
25 |
26 | WORKDIR /app
27 |
28 | COPY --from=builder /app/src/target/poli-0.12.2.jar /app/poli-0.12.2.jar
29 | COPY --from=builder /app/src/db/poli.db /app/db/poli.db
30 | COPY --from=builder /app/src/build_release/start.sh /app/start.sh
31 | COPY --from=builder /app/src/config/poli.docker.properties /app/config/poli.properties
32 |
33 | EXPOSE 6688
34 |
35 | RUN mkdir /app/jdbc-drivers
36 | RUN chmod +x /app/start.sh
37 |
38 | ENTRYPOINT ["/app/start.sh"]
39 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Zhonglu Wang
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # **Poli(魄力)**
2 |
3 | [](#)
4 | [](https://opensource.org/licenses/MIT)
5 | [](https://github.com/shzlw/poli/releases)
6 | [](https://cloud.docker.com/u/zhonglu/repository/docker/zhonglu/poli)
7 | [](https://travis-ci.org/shzlw/poli)
8 | [](https://codecov.io/gh/shzlw/poli)
9 |
10 | Poli is an easy-to-use SQL reporting application built for SQL lovers!
11 |
12 | ## Why Poli
13 |
14 | #### :zap: Self-hosted & easy setup
15 | Platform independent web application. Single JAR file + Single SQLite DB file. Get up and running in 5 minutes.
16 | #### :muscle: Connect to any database supporting JDBC drivers
17 | PostgreSQL, Oracle, SQL Server, MySQL, Elasticsearch... You name it.
18 | #### :bulb: SQL editor & schema viewer
19 | No ETLs, no generated SQL, polish your own SQL query to transform data.
20 | #### :fire: Rich and flexible styling
21 | Pixel-perfect positioning + Drag'n'Drop support to customize the reports and charts in your own way.
22 | #### :bookmark_tabs: Interactive Adhoc report
23 | Utilize the power of dynamic SQL with query variables to connect Filters and Charts.
24 | #### :hourglass: Canned report
25 | Capture the snapshot of historical data. Free up space in your own database.
26 | #### :santa: User management
27 | Three system level role configurations + Group based report access control.
28 | #### :earth_americas: Internationalization
29 | Custom the language pack and translations just for your audience.
30 | #### :moneybag: MIT license
31 | Open and free for all usages.
32 | #### :gem: Is that all?
33 | Auto refresh, drill through, fullscreen, embeds, color themes + more features in development.
34 |
35 | ## What's New ([latest](https://shzlw.github.io/poli/#/change-logs))
36 |
37 |
38 | 
39 |
40 | ## Gallery
41 |
42 | #### Slicer & Charts
43 |
44 | 
45 |
46 | #### Move & Resize
47 |
48 | 
49 |
50 | #### Color palette switch & export CSV
51 |
52 | 
53 |
54 | ## Quick Installation
55 |
56 | Windows/Linux
57 |
58 | ```sh
59 | java -jar poli-0.12.2.jar
60 | ```
61 |
62 | Docker
63 |
64 | ```sh
65 | docker run -d -p 6688:6688 --name poli zhonglu/poli:0.12.2
66 | ```
67 |
68 | Check [installation guide](https://shzlw.github.io/poli/#/installation) for more details.
69 |
70 | ## Download
71 |
72 | [Download](https://github.com/shzlw/poli/releases) the latest version of Poli via the github release page.
73 |
74 | ## Documentation
75 |
76 | Poli's documentation and other information can be found at [here](https://shzlw.github.io/poli/).
77 |
78 | ## Run on GCP
79 |
80 | [](https://deploy.cloud.run)
81 |
82 | ## License
83 |
84 | MIT License
85 |
86 | Copyright (c) Zhonglu Wang
--------------------------------------------------------------------------------
/build.bat:
--------------------------------------------------------------------------------
1 | rmdir /S /Q src\main\resources\static\static
2 | del /F /Q src\main\resources\static\favicon.ico
3 | del /F /Q src\main\resources\static\index.html
4 | cd web-app
5 | call npm install
6 | call npm run build
7 | cd ..
8 | xcopy web-app\build\static src\main\resources\static\static /s /i
9 | xcopy web-app\build\favicon.ico src\main\resources\static
10 | xcopy web-app\build\index.html src\main\resources\static
11 | call mvn clean package -DskipTests
12 |
--------------------------------------------------------------------------------
/build.sh:
--------------------------------------------------------------------------------
1 | rm -rf src/main/resources/static
2 | cd web-app
3 | npm install
4 | npm run build
5 | mkdir ../src/main/resources/static
6 | cp -r build/* ../src/main/resources/static/
7 |
8 | cd ..
9 | mvn clean package -DskipTests
10 |
--------------------------------------------------------------------------------
/build_dev.bat:
--------------------------------------------------------------------------------
1 | rmdir /S /Q src\main\resources\static\static
2 | del /F /Q src\main\resources\static\favicon.ico
3 | del /F /Q src\main\resources\static\index.html
4 | cd web-app
5 | call npm run build
6 | cd ..
7 | xcopy web-app\build\static src\main\resources\static\static /s /i
8 | xcopy web-app\build\favicon.ico src\main\resources\static
9 | xcopy web-app\build\index.html src\main\resources\static
10 | call mvn clean package -DskipTests
11 |
--------------------------------------------------------------------------------
/build_dev.sh:
--------------------------------------------------------------------------------
1 | rm -rf src/main/resources/static/*
2 | cd web-app
3 | npm run build
4 | cp -r build/* ../src/main/resources/static/
5 | cd ..
6 | mvn clean package -DskipTests
7 |
--------------------------------------------------------------------------------
/build_release/build_release.sh:
--------------------------------------------------------------------------------
1 | VERSION=$(./version)
2 | if [ $# -eq 0 ]; then
3 | echo "No arguments specified. Use default version: $VERSION"
4 | else
5 | VERSION=$1
6 | echo "Version entered: $VERSION"
7 | fi
8 |
9 | release=poli-$VERSION
10 | mkdir $release
11 |
12 | rm -rf src/main/resources/static
13 | cd web-app
14 | npm install
15 | npm run build
16 | mkdir ../src/main/resources/static
17 | cp -r build/* ../src/main/resources/static/
18 | cd ..
19 | mvn clean package -DskipTests
20 |
21 | cp -r export-server $release
22 | cp target/poli-*.jar $release
23 | cp -r docs $release
24 | cp LICENSE $release
25 | cp -r third-party-license $release
26 | cp build_release/start.sh $release
27 | cp build_release/start.bat $release
28 | chmod 755 $release/start.sh
29 | chmod 755 $release/start.bat
30 | cp README.md $release
31 | cp -r upgrade $release
32 | mkdir $release/jdbc-drivers
33 | mkdir $release/config
34 | cp config/poli.properties $release/config/poli.properties
35 | mkdir $release/db
36 | cd $release/db
37 | sqlite3 poli.db < ../../db/schema-sqlite.sql
38 | chmod 755 poli.db
39 | cd ../..
40 | mv $release release
41 |
42 | cd release
43 | zip -r $release.zip $release/
44 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/build_release/start.bat:
--------------------------------------------------------------------------------
1 | java -jar poli-0.12.2.jar --spring.config.name=application,poli
--------------------------------------------------------------------------------
/build_release/start.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e
3 |
4 | java -jar poli-0.12.2.jar --spring.config.name=application,poli
5 |
--------------------------------------------------------------------------------
/config/poli.docker.properties:
--------------------------------------------------------------------------------
1 |
2 | # Config the absolute path to poli.db
3 | # For example, jdbc:sqlite:/home/poli/poli.db or jdbc:sqlite:c:/poli/poli.db
4 | spring.datasource.url=jdbc:sqlite:/app/db/poli.db
5 |
6 | # The maximum number of data source pool size
7 | # poli.datasource-maximum-pool-size=50
8 |
9 | # The maximum number of records returned in the JDBC result set. Default value is -1 which returns unlimited rows.
10 | # poli.maximum-query-records=-1
11 |
12 | # The display language.
13 | # poli.locale-language=en
14 |
15 | # Allow to run multiple SQL query statements in the query editor. If there are multiple SQL statements, only the
16 | # last one will return query results.
17 | # poli.allow-multiple-query-statements=false
18 |
19 | # PDF export server URL. Check export-server/poli-export-server.js for more details.
20 | # poli.export-server-url=http://127.0.0.1:6689/pdf
21 |
--------------------------------------------------------------------------------
/config/poli.properties:
--------------------------------------------------------------------------------
1 |
2 | # Config the absolute path to poli.db
3 | # For example, jdbc:sqlite:/home/poli/poli.db or jdbc:sqlite:c:/poli/poli.db
4 | # spring.datasource.url=
5 |
6 | # The maximum number of data source pool size
7 | # poli.datasource-maximum-pool-size=50
8 |
9 | # The maximum number of records returned in the JDBC result set. Default value is -1 which returns unlimited rows.
10 | # poli.maximum-query-records=-1
11 |
12 | # The display language.
13 | # poli.locale-language=en
14 |
15 | # Allow to run multiple SQL query statements in the query editor. If there are multiple SQL statements, only the
16 | # last one will return query results.
17 | # poli.allow-multiple-query-statements=false
18 |
19 | # PDF export server URL. Check export-server/poli-export-server.js for more details.
20 | # poli.export-server-url=http://127.0.0.1:6689/pdf
--------------------------------------------------------------------------------
/db/poli.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shzlw/poli/5b90d6adc6f839866cb77854ad33f3cd9e1e26e8/db/poli.db
--------------------------------------------------------------------------------
/docs/.nojekyll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shzlw/poli/5b90d6adc6f839866cb77854ad33f3cd9e1e26e8/docs/.nojekyll
--------------------------------------------------------------------------------
/docs/_coverpage.md:
--------------------------------------------------------------------------------
1 | #
Poli
2 |
3 | > An easy-to-use SQL reporting application built for SQL lovers
4 |
5 | [GitHub](https://github.com/shzlw/poli)
6 | [Get Started](#Poli)
7 |
8 | 
--------------------------------------------------------------------------------
/docs/_images/screenshots/apikey.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shzlw/poli/5b90d6adc6f839866cb77854ad33f3cd9e1e26e8/docs/_images/screenshots/apikey.jpg
--------------------------------------------------------------------------------
/docs/_images/screenshots/apikey2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shzlw/poli/5b90d6adc6f839866cb77854ad33f3cd9e1e26e8/docs/_images/screenshots/apikey2.jpg
--------------------------------------------------------------------------------
/docs/_images/screenshots/autorefresh.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shzlw/poli/5b90d6adc6f839866cb77854ad33f3cd9e1e26e8/docs/_images/screenshots/autorefresh.jpg
--------------------------------------------------------------------------------
/docs/_images/screenshots/canned_report.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shzlw/poli/5b90d6adc6f839866cb77854ad33f3cd9e1e26e8/docs/_images/screenshots/canned_report.jpg
--------------------------------------------------------------------------------
/docs/_images/screenshots/component_card.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shzlw/poli/5b90d6adc6f839866cb77854ad33f3cd9e1e26e8/docs/_images/screenshots/component_card.jpg
--------------------------------------------------------------------------------
/docs/_images/screenshots/component_funnel.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shzlw/poli/5b90d6adc6f839866cb77854ad33f3cd9e1e26e8/docs/_images/screenshots/component_funnel.jpg
--------------------------------------------------------------------------------
/docs/_images/screenshots/component_heatmap.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shzlw/poli/5b90d6adc6f839866cb77854ad33f3cd9e1e26e8/docs/_images/screenshots/component_heatmap.jpg
--------------------------------------------------------------------------------
/docs/_images/screenshots/component_treemap.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shzlw/poli/5b90d6adc6f839866cb77854ad33f3cd9e1e26e8/docs/_images/screenshots/component_treemap.jpg
--------------------------------------------------------------------------------
/docs/_images/screenshots/date_picker.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shzlw/poli/5b90d6adc6f839866cb77854ad33f3cd9e1e26e8/docs/_images/screenshots/date_picker.jpg
--------------------------------------------------------------------------------
/docs/_images/screenshots/drillthrough1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shzlw/poli/5b90d6adc6f839866cb77854ad33f3cd9e1e26e8/docs/_images/screenshots/drillthrough1.jpg
--------------------------------------------------------------------------------
/docs/_images/screenshots/drillthrough2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shzlw/poli/5b90d6adc6f839866cb77854ad33f3cd9e1e26e8/docs/_images/screenshots/drillthrough2.jpg
--------------------------------------------------------------------------------
/docs/_images/screenshots/drillthrough3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shzlw/poli/5b90d6adc6f839866cb77854ad33f3cd9e1e26e8/docs/_images/screenshots/drillthrough3.jpg
--------------------------------------------------------------------------------
/docs/_images/screenshots/drillthrough4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shzlw/poli/5b90d6adc6f839866cb77854ad33f3cd9e1e26e8/docs/_images/screenshots/drillthrough4.jpg
--------------------------------------------------------------------------------
/docs/_images/screenshots/ds1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shzlw/poli/5b90d6adc6f839866cb77854ad33f3cd9e1e26e8/docs/_images/screenshots/ds1.jpg
--------------------------------------------------------------------------------
/docs/_images/screenshots/ds2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shzlw/poli/5b90d6adc6f839866cb77854ad33f3cd9e1e26e8/docs/_images/screenshots/ds2.jpg
--------------------------------------------------------------------------------
/docs/_images/screenshots/export_csv.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shzlw/poli/5b90d6adc6f839866cb77854ad33f3cd9e1e26e8/docs/_images/screenshots/export_csv.jpg
--------------------------------------------------------------------------------
/docs/_images/screenshots/export_to_pdf_button.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shzlw/poli/5b90d6adc6f839866cb77854ad33f3cd9e1e26e8/docs/_images/screenshots/export_to_pdf_button.jpg
--------------------------------------------------------------------------------
/docs/_images/screenshots/export_to_pdf_dialog.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shzlw/poli/5b90d6adc6f839866cb77854ad33f3cd9e1e26e8/docs/_images/screenshots/export_to_pdf_dialog.jpg
--------------------------------------------------------------------------------
/docs/_images/screenshots/favourite.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shzlw/poli/5b90d6adc6f839866cb77854ad33f3cd9e1e26e8/docs/_images/screenshots/favourite.jpg
--------------------------------------------------------------------------------
/docs/_images/screenshots/fullscreen1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shzlw/poli/5b90d6adc6f839866cb77854ad33f3cd9e1e26e8/docs/_images/screenshots/fullscreen1.jpg
--------------------------------------------------------------------------------
/docs/_images/screenshots/fullscreen2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shzlw/poli/5b90d6adc6f839866cb77854ad33f3cd9e1e26e8/docs/_images/screenshots/fullscreen2.jpg
--------------------------------------------------------------------------------
/docs/_images/screenshots/group1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shzlw/poli/5b90d6adc6f839866cb77854ad33f3cd9e1e26e8/docs/_images/screenshots/group1.jpg
--------------------------------------------------------------------------------
/docs/_images/screenshots/group2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shzlw/poli/5b90d6adc6f839866cb77854ad33f3cd9e1e26e8/docs/_images/screenshots/group2.jpg
--------------------------------------------------------------------------------
/docs/_images/screenshots/login.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shzlw/poli/5b90d6adc6f839866cb77854ad33f3cd9e1e26e8/docs/_images/screenshots/login.jpg
--------------------------------------------------------------------------------
/docs/_images/screenshots/login2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shzlw/poli/5b90d6adc6f839866cb77854ad33f3cd9e1e26e8/docs/_images/screenshots/login2.jpg
--------------------------------------------------------------------------------
/docs/_images/screenshots/login3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shzlw/poli/5b90d6adc6f839866cb77854ad33f3cd9e1e26e8/docs/_images/screenshots/login3.jpg
--------------------------------------------------------------------------------
/docs/_images/screenshots/more_options_button.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shzlw/poli/5b90d6adc6f839866cb77854ad33f3cd9e1e26e8/docs/_images/screenshots/more_options_button.jpg
--------------------------------------------------------------------------------
/docs/_images/screenshots/report1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shzlw/poli/5b90d6adc6f839866cb77854ad33f3cd9e1e26e8/docs/_images/screenshots/report1.jpg
--------------------------------------------------------------------------------
/docs/_images/screenshots/report10.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shzlw/poli/5b90d6adc6f839866cb77854ad33f3cd9e1e26e8/docs/_images/screenshots/report10.jpg
--------------------------------------------------------------------------------
/docs/_images/screenshots/report11.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shzlw/poli/5b90d6adc6f839866cb77854ad33f3cd9e1e26e8/docs/_images/screenshots/report11.jpg
--------------------------------------------------------------------------------
/docs/_images/screenshots/report12.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shzlw/poli/5b90d6adc6f839866cb77854ad33f3cd9e1e26e8/docs/_images/screenshots/report12.jpg
--------------------------------------------------------------------------------
/docs/_images/screenshots/report13.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shzlw/poli/5b90d6adc6f839866cb77854ad33f3cd9e1e26e8/docs/_images/screenshots/report13.jpg
--------------------------------------------------------------------------------
/docs/_images/screenshots/report14.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shzlw/poli/5b90d6adc6f839866cb77854ad33f3cd9e1e26e8/docs/_images/screenshots/report14.jpg
--------------------------------------------------------------------------------
/docs/_images/screenshots/report2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shzlw/poli/5b90d6adc6f839866cb77854ad33f3cd9e1e26e8/docs/_images/screenshots/report2.jpg
--------------------------------------------------------------------------------
/docs/_images/screenshots/report3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shzlw/poli/5b90d6adc6f839866cb77854ad33f3cd9e1e26e8/docs/_images/screenshots/report3.jpg
--------------------------------------------------------------------------------
/docs/_images/screenshots/report4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shzlw/poli/5b90d6adc6f839866cb77854ad33f3cd9e1e26e8/docs/_images/screenshots/report4.jpg
--------------------------------------------------------------------------------
/docs/_images/screenshots/report4_1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shzlw/poli/5b90d6adc6f839866cb77854ad33f3cd9e1e26e8/docs/_images/screenshots/report4_1.jpg
--------------------------------------------------------------------------------
/docs/_images/screenshots/report5.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shzlw/poli/5b90d6adc6f839866cb77854ad33f3cd9e1e26e8/docs/_images/screenshots/report5.jpg
--------------------------------------------------------------------------------
/docs/_images/screenshots/report6.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shzlw/poli/5b90d6adc6f839866cb77854ad33f3cd9e1e26e8/docs/_images/screenshots/report6.jpg
--------------------------------------------------------------------------------
/docs/_images/screenshots/report7.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shzlw/poli/5b90d6adc6f839866cb77854ad33f3cd9e1e26e8/docs/_images/screenshots/report7.jpg
--------------------------------------------------------------------------------
/docs/_images/screenshots/report8.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shzlw/poli/5b90d6adc6f839866cb77854ad33f3cd9e1e26e8/docs/_images/screenshots/report8.jpg
--------------------------------------------------------------------------------
/docs/_images/screenshots/report9.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shzlw/poli/5b90d6adc6f839866cb77854ad33f3cd9e1e26e8/docs/_images/screenshots/report9.jpg
--------------------------------------------------------------------------------
/docs/_images/screenshots/rls_table.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shzlw/poli/5b90d6adc6f839866cb77854ad33f3cd9e1e26e8/docs/_images/screenshots/rls_table.jpg
--------------------------------------------------------------------------------
/docs/_images/screenshots/rls_user_attributes.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shzlw/poli/5b90d6adc6f839866cb77854ad33f3cd9e1e26e8/docs/_images/screenshots/rls_user_attributes.jpg
--------------------------------------------------------------------------------
/docs/_images/screenshots/share_button.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shzlw/poli/5b90d6adc6f839866cb77854ad33f3cd9e1e26e8/docs/_images/screenshots/share_button.jpg
--------------------------------------------------------------------------------
/docs/_images/screenshots/share_dialog.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shzlw/poli/5b90d6adc6f839866cb77854ad33f3cd9e1e26e8/docs/_images/screenshots/share_dialog.jpg
--------------------------------------------------------------------------------
/docs/_images/screenshots/share_event.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shzlw/poli/5b90d6adc6f839866cb77854ad33f3cd9e1e26e8/docs/_images/screenshots/share_event.jpg
--------------------------------------------------------------------------------
/docs/_images/screenshots/slicer.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shzlw/poli/5b90d6adc6f839866cb77854ad33f3cd9e1e26e8/docs/_images/screenshots/slicer.jpg
--------------------------------------------------------------------------------
/docs/_images/screenshots/style1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shzlw/poli/5b90d6adc6f839866cb77854ad33f3cd9e1e26e8/docs/_images/screenshots/style1.jpg
--------------------------------------------------------------------------------
/docs/_images/screenshots/style2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shzlw/poli/5b90d6adc6f839866cb77854ad33f3cd9e1e26e8/docs/_images/screenshots/style2.jpg
--------------------------------------------------------------------------------
/docs/_images/screenshots/user1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shzlw/poli/5b90d6adc6f839866cb77854ad33f3cd9e1e26e8/docs/_images/screenshots/user1.jpg
--------------------------------------------------------------------------------
/docs/_images/screenshots/user2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shzlw/poli/5b90d6adc6f839866cb77854ad33f3cd9e1e26e8/docs/_images/screenshots/user2.jpg
--------------------------------------------------------------------------------
/docs/_images/screenshots/user3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shzlw/poli/5b90d6adc6f839866cb77854ad33f3cd9e1e26e8/docs/_images/screenshots/user3.jpg
--------------------------------------------------------------------------------
/docs/_images/screenshots/user4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shzlw/poli/5b90d6adc6f839866cb77854ad33f3cd9e1e26e8/docs/_images/screenshots/user4.jpg
--------------------------------------------------------------------------------
/docs/_media/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shzlw/poli/5b90d6adc6f839866cb77854ad33f3cd9e1e26e8/docs/_media/favicon.ico
--------------------------------------------------------------------------------
/docs/_media/style.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --theme-color: #0065FF;
3 | --theme-color-light: #2684FF;
4 | --theme-color-dark: #0052CC;
5 |
6 | --text-color-base: #091E42;
7 | --text-color-secondary: #42526E;
8 | --text-color-tertiary: #7A869A;
9 | }
10 |
11 | body {
12 | font-family: Tahoma, Geneva, sans-serif;
13 | color: #172B4D;
14 | font-size: 16px;
15 | }
16 |
17 | * {
18 | text-decoration: none !important;
19 | }
20 |
21 | a {
22 | transition: all 0.2s linear;
23 | }
24 |
25 | .sidebar .app-name {
26 | font-weight: 700;
27 | }
28 |
29 | .sidebar li {
30 | margin: 5px 0px;
31 | }
32 |
33 | .sidebar li>p {
34 | font-size: 16px;
35 | }
36 |
37 | .sidebar ul li a {
38 | color: #42526E;
39 | font-size: 15px;
40 | }
41 |
42 | .markdown-section pre,
43 | .markdown-section pre > code {
44 | background-color: #253858;
45 | color: #EBECF0;
46 | font-size: 15px;
47 | }
48 |
49 | .markdown-section code {
50 | display: inline-block;
51 | }
52 |
53 | .markdown-section pre {
54 | padding: 6px 12px;
55 | }
56 |
57 | .markdown-section pre > code {
58 | padding: 0;
59 | }
--------------------------------------------------------------------------------
/docs/_sidebar.md:
--------------------------------------------------------------------------------
1 | - Getting Started
2 |
3 | - [Installation](installation.md)
4 | - [Quick Start](quick-start.md)
5 | - [Configuration](configuration.md)
6 | - [Upgrade](upgrade.md)
7 |
8 | - Basic Concepts
9 |
10 | - [Data Source](data-source.md)
11 | - [Report](report.md)
12 | - [Report Component](report-component.md)
13 | - [User Management](user-management.md)
14 |
15 | - Development
16 |
17 | - [Build](build.md)
18 |
19 | - Release
20 |
21 | - [Change logs](change-logs.md)
--------------------------------------------------------------------------------
/docs/build.md:
--------------------------------------------------------------------------------
1 | # Build
2 |
3 | ## Main tech stack
4 |
5 | * Spring Boot 2
6 | * React 16
7 | * SQLite 3
8 |
9 | ## Prerequisite
10 |
11 | * Node.js
12 | * Maven
13 | * SQLite 3 CLI
14 | * JDK 1.8+
15 |
16 | ## Building
17 |
18 | 1. Clone or download the [repository](https://github.com/shzlw/poli).
19 |
20 | 2. Run the build script.
21 |
22 | ```
23 | # Windows
24 | build.bat
25 |
26 | # Linux
27 | ./build.sh
28 | ```
29 |
30 | 3. The java executable poli-x.y.z.jar is generated under the target folder. Run this to start the server.
31 |
32 | ```
33 | java -jar target\poli-x.y.z.jar
34 | ```
35 |
36 | ## Source code structure
37 |
38 | ```
39 | |-- Repository
40 | |-- config
41 | |-- poli.properties
42 | |-- db
43 | |-- schema-sqlite.sql
44 | |-- src
45 | |-- web-app
46 | |-- pom.xml
47 | |-- build.sh
48 | |-- build.bat
49 | |...
50 | ```
51 |
52 | * Src folder contains the java source code.
53 | * Web-app folder contains the react code.
54 | * Schema-sqlite.sql contains the database schema for SQLite 3. The poli.db file in the release is loaded from this schema.
55 | * The build scripts build the code in web-app first, copy the generated files to src ~/static folder and finally run maven to build the java project.
56 |
57 |
--------------------------------------------------------------------------------
/docs/configuration.md:
--------------------------------------------------------------------------------
1 | # Configuration
2 |
3 | Application configurations can be adjusted in poli.properties.
4 |
5 | ## Data source path
6 |
7 | Absolute path to access poli.db in your local environment.
8 |
9 | ```
10 | # Linux
11 | spring.datasource.url=jdbc:sqlite:/home/poli/poli.db
12 |
13 | # Windows
14 | spring.datasource.url=jdbc:sqlite:c:/poli/poli.db
15 | ```
16 |
17 | ## Data source pool size
18 |
19 | The maximum number of data source pool size.
20 |
21 | ```
22 | poli.datasource-maximum-pool-size=50
23 | ```
24 |
25 | ## Number of query results
26 |
27 | The maximum number of records returned in the JDBC result set. Default value is -1 which returns unlimited rows
28 |
29 | ```
30 | poli.maximum-query-records=1000
31 | ```
32 |
33 | ## All multiple SQL statements
34 |
35 | Allow to run multiple SQL query statements in the query editor. If there are multiple SQL statements, only the last one will return query results.
36 |
37 | ```
38 | poli.allow-multiple-query-statements=true
39 | ```
40 |
41 | For example, when query against PostgreSQL, set the search_path in the first statement and query in the second statement.
42 |
43 | ```sql
44 | set search_path to MY_SCHEMA;
45 | select * from MY_TABLE;
46 | ```
47 |
48 | ## Internationalization (i18n)
49 |
50 | Currently Poli supports four languages:
51 |
52 | * English
53 | * Chinese
54 | * Spanish
55 | * French
56 |
57 | The default language can be switched via changing the poli.properties
58 |
59 | ```
60 | # English
61 | poli.locale-language=en
62 |
63 | # Chinese
64 | poli.locale-language=zh
65 |
66 | # Spanish
67 | poli.locale-language=es
68 |
69 | # French
70 | poli.locale-language=fr
71 | ```
72 |
73 | ## Steps to add your own translations
74 |
75 | For instance, add Spanish language: es
76 |
77 | 1. Use poli/web-app/src/locales/en/translation.json as a base template. Copy the content and create a new file at poli/web-app/src/locales/es/translation.json.
78 |
79 | 2. Modify the translations in poli/web-app/src/locales/es/translation.json.
80 |
81 | 3. Modify poli/web-app/src/i18n.js
82 |
83 | ```javascript
84 | import translationES from './locales/es/translation';
85 |
86 | const resources = {
87 | es: {
88 | translation: translationES
89 | }
90 | }
91 | ```
92 |
93 | 4. Modify poli.properties
94 |
95 | ```
96 | poli.locale-language=es
97 | ```
98 |
99 | 5. Rebuild the project
100 |
101 | Check [Build](build) for more details.
102 |
103 |
104 |
105 |
106 |
--------------------------------------------------------------------------------
/docs/data-source.md:
--------------------------------------------------------------------------------
1 | # Data Source
2 |
3 | Poli connects to database through the JDBC interface. You can try connect to any databases that supports JDBC drivers. To configure Poli to connect to different databases, two steps are required.
4 |
5 | ## Download the JDBC jar file
6 |
7 | Download the JDBC jar files based on the database you'd like to connect to and put those JDBC jar files under /jdbc-drivers.
8 |
9 | For example:
10 | ```sh
11 | |-- jdbc-drivers
12 | |-- postgresql-42.2.5.jar
13 | |-- mysql-connector-java-8.0.12.jar
14 | |-- mssql-jdbc-7.2.0.jre8.jar
15 | |...
16 | ```
17 |
18 | > There are no JDBC drivers included in the release except the JDBC driver for SQLite.
19 |
20 | ## Create a Data Source
21 |
22 | For instance, information needed to connect to a PostgreSQL database
23 |
24 | ```
25 | Connection Url: jdbc:postgresql://127.0.0.1:5432/testdb
26 | Driver Class Name: org.postgresql.Driver
27 | Username: postgres
28 | Password: test
29 | Ping: SELECT 1
30 | ```
31 |
32 | MySQL
33 | ```
34 | Connection Url: jdbc:mysql://127.0.0.1:3306/testdb
35 | Driver Class Name: com.mysql.jdbc.Driver
36 | Ping: SELECT 1
37 |
38 | -- If use MySQL Connector/J 8.0 or higher
39 | Driver Class Name: com.mysql.cj.jdbc.Driver
40 | ```
41 |
42 | SQL Server
43 |
44 | ```
45 | Connection Url: jdbc:sqlserver://127.0.0.1:1433;databaseName=testdb
46 | Driver Class Name: com.microsoft.sqlserver.jdbc.SQLServerDriver
47 | Ping: SELECT 1
48 | ```
49 |
50 | > Use the ping button to test the connection.
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Poli
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/docs/upgrade.md:
--------------------------------------------------------------------------------
1 | # Upgrade
2 |
3 | ## Windows/Linux
4 |
5 | 1. Download and unzip poli-x.y.z.zip.
6 | 2. Stop the Poli server.
7 | 3. Backup the old poli.db. (Simply copying and saving the poli.db as a new file should work)
8 | 4. Apply the upgrade sql if needed.
9 | ```
10 | ./sqlite3 poli.db
11 | sqlite> .read poli_upgrade_x.y.z.sql
12 | ```
13 | 5. Add the new configuration values if needed.
14 | 6. Replace the old poli-old-version.jar with the new poli-new-version.jar.
15 | 7. Start the Poli server.
16 |
--------------------------------------------------------------------------------
/docs/user-management.md:
--------------------------------------------------------------------------------
1 | # User Management
2 |
3 | ## System role
4 |
5 | There are three types of system roles in Poli. Each user account requires to be assigned a system role when the account is created.
6 |
7 | #### Admin
8 |
9 | * Only one per instance. Cannot be created.
10 | * Manage reports
11 | * Manage datasources
12 | * Manage users
13 |
14 | #### Developer
15 | * Manage reports
16 | * Manage datasources
17 | * Manage users in Viewer Role
18 |
19 | #### Viewer
20 | * Access reports
21 |
22 | ## Group
23 |
24 | Group is used to control the access level to reports for Viewer user. Reports can be assigned to Group so the users in Viewer Role who belong to that group can have access.
25 |
26 | For example:
27 |
28 | ```
29 | * User u1 belongs to Group g1
30 | * User u2 belongs to Group g2
31 | * Report r1 is assigned to Group g1
32 | * Report r1, r2 and r3 are assigned to Group g2
33 | * User u1 has access to Report r1 only
34 | * user u2 has access to Report r1, r2 and r3
35 | ```
--------------------------------------------------------------------------------
/export-server/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/export-server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "poli-export-server",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "",
10 | "license": "ISC",
11 | "dependencies": {
12 | "puppeteer": "^3.0.4"
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/export-server/poli-export-server.js:
--------------------------------------------------------------------------------
1 | const http = require('http');
2 | const puppeteer = require('puppeteer');
3 |
4 | const poli = 'http://localhost:6688/poli';
5 | const hostname = '127.0.0.1';
6 | const port = 6689;
7 |
8 | const server = http.createServer((request, response) => {
9 | const {
10 | url,
11 | method
12 | } = request;
13 | if (url === '/pdf' && method === 'POST') {
14 | pdfHandler(request).then(pdf => {
15 | response.setHeader('Content-Type', 'application/pdf');
16 | response.setHeader('Content-Length', pdf.length);
17 | response.end(Buffer.from(pdf));
18 | }).catch(error => {
19 | console.log(error);
20 | })
21 | } else {
22 | response.end('Hello Poli!');
23 | }
24 | });
25 |
26 | async function pdfHandler(request) {
27 | const body = await getPostBody(request);
28 | const exportRequest = JSON.parse(body);
29 | const pdf = await generatePDF(exportRequest);
30 | return pdf;
31 | }
32 |
33 | function getPostBody(request) {
34 | return new Promise((resolve, reject) => {
35 | let body = '';
36 | request.on('data', (chunk) => {
37 | body += chunk;
38 | if (body.length > 1e8) {
39 | request.connection.destroy();
40 | }
41 | });
42 |
43 | request.on('end', () => {
44 | resolve(body);
45 | });
46 |
47 | request.on("error", (err) => {
48 | reject(err);
49 | });
50 | });
51 | }
52 |
53 |
54 | async function generatePDF(exportRequest) {
55 | const tomorrow = new Date();
56 | tomorrow.setDate(tomorrow.getDate() + 1);
57 |
58 | const width = parseInt(exportRequest.width, 10);
59 | const height = parseInt(exportRequest.height, 10);
60 |
61 | const browser = await puppeteer.launch();
62 | const page = await browser.newPage();
63 | const cookie = {
64 | name: 'pskey',
65 | value: exportRequest.sessionKey,
66 | domain: 'localhost',
67 | path: '/',
68 | expires: Math.floor(tomorrow.getTime() / 1000)
69 | };
70 | await page.setCookie(cookie);
71 | await page.setViewport({ width: width, height: height });
72 | await page.goto(poli + '/workspace/report/fullscreen?$showControl=false&$toReport=' + exportRequest.reportName, {waitUntil: 'networkidle2'});
73 | await page.waitFor(2000);
74 | const pdf = await page.pdf({
75 | width: width,
76 | height: height,
77 | printBackground: true
78 | });
79 | await browser.close();
80 | return pdf;
81 | }
82 |
83 | server.listen(port, hostname, () => {
84 | console.log(`Server running at http://${hostname}:${port}/`);
85 | });
86 |
87 |
88 |
--------------------------------------------------------------------------------
/src/main/java/com/shzlw/poli/App.java:
--------------------------------------------------------------------------------
1 | package com.shzlw.poli;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 |
6 | @SpringBootApplication
7 | public class App {
8 |
9 | public static void main(String[] args) {
10 | SpringApplication.run(App.class, args);
11 | }
12 | }
--------------------------------------------------------------------------------
/src/main/java/com/shzlw/poli/AppStartUpListener.java:
--------------------------------------------------------------------------------
1 | package com.shzlw.poli;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 | import org.springframework.boot.context.event.ApplicationReadyEvent;
6 | import org.springframework.context.event.EventListener;
7 | import org.springframework.stereotype.Component;
8 |
9 | @Component
10 | public class AppStartUpListener {
11 |
12 | private static final Logger LOGGER = LoggerFactory.getLogger(AppStartUpListener.class);
13 |
14 | @EventListener(ApplicationReadyEvent.class)
15 | public void applicationReadyEvent () {
16 | LOGGER.info("Welcome from Poli!");
17 | }
18 | }
--------------------------------------------------------------------------------
/src/main/java/com/shzlw/poli/config/AppConfig.java:
--------------------------------------------------------------------------------
1 | package com.shzlw.poli.config;
2 |
3 | import com.shzlw.poli.filter.AuthFilter;
4 | import okhttp3.OkHttpClient;
5 | import org.springframework.boot.web.servlet.FilterRegistrationBean;
6 | import org.springframework.context.annotation.Bean;
7 | import org.springframework.context.annotation.Configuration;
8 | import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
9 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
10 |
11 | import java.util.concurrent.TimeUnit;
12 |
13 | @Configuration
14 | public class AppConfig implements WebMvcConfigurer {
15 |
16 | @Override
17 | public void addViewControllers(ViewControllerRegistry registry) {
18 | // \\w+ regex does not match the hyphen or underscore. Replace \\w for ^[a-zA-Z\d-_]
19 | registry.addViewController("/{spring:\\w+}")
20 | .setViewName("forward:/");
21 | registry.addViewController("/**/{spring:\\w+}")
22 | .setViewName("forward:/");
23 | registry.addViewController("/{spring:\\w+}/**{spring:?!(\\.js|\\.css)$}")
24 | .setViewName("forward:/");
25 | }
26 |
27 | @Bean
28 | public FilterRegistrationBean authFilterRegistry() {
29 | FilterRegistrationBean registration = new FilterRegistrationBean();
30 | registration.setName("authFilter");
31 | registration.setFilter(new AuthFilter());
32 | registration.addUrlPatterns("/*");
33 | registration.setAsyncSupported(Boolean.TRUE);
34 | registration.setEnabled(Boolean.TRUE);
35 | return registration;
36 | }
37 |
38 | @Bean
39 | public OkHttpClient okHttpClient() {
40 | return new OkHttpClient.Builder()
41 | .connectTimeout(30, TimeUnit.SECONDS)
42 | .writeTimeout(30, TimeUnit.SECONDS)
43 | .readTimeout(30, TimeUnit.SECONDS)
44 | .build();
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/main/java/com/shzlw/poli/config/AppProperties.java:
--------------------------------------------------------------------------------
1 | package com.shzlw.poli.config;
2 |
3 | import org.springframework.boot.context.properties.ConfigurationProperties;
4 | import org.springframework.stereotype.Component;
5 |
6 | @Component
7 | @ConfigurationProperties(prefix = "poli")
8 | public class AppProperties {
9 |
10 | Integer datasourceMaximumPoolSize;
11 |
12 | Integer maximumQueryRecords;
13 |
14 | String localeLanguage;
15 |
16 | Boolean allowMultipleQueryStatements;
17 |
18 | String exportServerUrl;
19 |
20 | public AppProperties() {
21 | datasourceMaximumPoolSize = 50;
22 | maximumQueryRecords = 1000;
23 | localeLanguage = "en";
24 | allowMultipleQueryStatements = false;
25 | }
26 |
27 | public Integer getDatasourceMaximumPoolSize() {
28 | return datasourceMaximumPoolSize;
29 | }
30 |
31 | public void setDatasourceMaximumPoolSize(Integer datasourceMaximumPoolSize) {
32 | this.datasourceMaximumPoolSize = datasourceMaximumPoolSize;
33 | }
34 |
35 | public Integer getMaximumQueryRecords() {
36 | return maximumQueryRecords;
37 | }
38 |
39 | public void setMaximumQueryRecords(Integer maximumQueryRecords) {
40 | this.maximumQueryRecords = maximumQueryRecords;
41 | }
42 |
43 | public String getLocaleLanguage() {
44 | return localeLanguage;
45 | }
46 |
47 | public void setLocaleLanguage(String localeLanguage) {
48 | this.localeLanguage = localeLanguage;
49 | }
50 |
51 | public Boolean getAllowMultipleQueryStatements() {
52 | return allowMultipleQueryStatements;
53 | }
54 |
55 | public void setAllowMultipleQueryStatements(Boolean allowMultipleQueryStatements) {
56 | this.allowMultipleQueryStatements = allowMultipleQueryStatements;
57 | }
58 |
59 | public String getExportServerUrl() {
60 | return exportServerUrl;
61 | }
62 |
63 | public void setExportServerUrl(String exportServerUrl) {
64 | this.exportServerUrl = exportServerUrl;
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/main/java/com/shzlw/poli/dao/AuditLogDao.java:
--------------------------------------------------------------------------------
1 | package com.shzlw.poli.dao;
2 |
3 | import com.shzlw.poli.model.AuditLog;
4 | import com.shzlw.poli.util.CommonUtils;
5 | import org.springframework.beans.factory.annotation.Autowired;
6 | import org.springframework.jdbc.core.JdbcTemplate;
7 | import org.springframework.jdbc.core.RowMapper;
8 | import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
9 | import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
10 | import org.springframework.jdbc.support.GeneratedKeyHolder;
11 | import org.springframework.jdbc.support.KeyHolder;
12 | import org.springframework.stereotype.Repository;
13 |
14 | import java.sql.ResultSet;
15 | import java.sql.SQLException;
16 | import java.util.List;
17 |
18 | @Repository
19 | public class AuditLogDao {
20 |
21 | @Autowired
22 | JdbcTemplate jt;
23 |
24 | @Autowired
25 | NamedParameterJdbcTemplate npjt;
26 |
27 | public List findAll(int page, int pageSize, String searchValue) {
28 | String sql = "SELECT id, created_at, type, data "
29 | + "FROM p_audit_log "
30 | + "WHERE data LIKE :data "
31 | + "OR type LIKE :type "
32 | + "ORDER BY created_at DESC LIMIT :limit OFFSET :offset";
33 |
34 | MapSqlParameterSource params = new MapSqlParameterSource();
35 | params.addValue("data", DaoHelper.getLikeParam(searchValue));
36 | params.addValue("type", DaoHelper.getLikeParam(searchValue));
37 | params.addValue("offset", DaoHelper.toOffset(page, pageSize));
38 | params.addValue("limit", DaoHelper.toLimit(pageSize));
39 |
40 | return npjt.query(sql, params, new AuditLogMapper());
41 | }
42 |
43 | public long insert(long createdAt, String type, String data) {
44 | String sql = "INSERT INTO p_audit_log(created_at, type, data) "
45 | + "VALUES(:created_at, :type, :data)";
46 | MapSqlParameterSource params = new MapSqlParameterSource();
47 | params.addValue(AuditLog.CREATED_AT, createdAt);
48 | params.addValue(AuditLog.TYPE, type);
49 | params.addValue(AuditLog.DATA, data);
50 |
51 | KeyHolder keyHolder = new GeneratedKeyHolder();
52 | npjt.update(sql, params, keyHolder, new String[] { AuditLog.ID });
53 | return keyHolder.getKey().longValue();
54 | }
55 |
56 | private static class AuditLogMapper implements RowMapper {
57 | @Override
58 | public AuditLog mapRow(ResultSet rs, int i) throws SQLException {
59 | AuditLog r = new AuditLog();
60 | r.setId(rs.getLong(AuditLog.ID));
61 | r.setType(rs.getString(AuditLog.TYPE));
62 | long createdAt = rs.getLong(AuditLog.CREATED_AT);
63 | r.setCreatedAt(CommonUtils.toReadableDateTime(CommonUtils.fromEpoch(createdAt)));
64 | r.setData(rs.getString(AuditLog.DATA));
65 | return r;
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/main/java/com/shzlw/poli/dao/DaoHelper.java:
--------------------------------------------------------------------------------
1 | package com.shzlw.poli.dao;
2 |
3 | public final class DaoHelper {
4 |
5 | public static final int DEFAULT_LIMIT = 20;
6 | public static final int DEFAULT_OFFSET = 0;
7 |
8 | private DaoHelper() {}
9 |
10 | public static int toOffset(int page, int pageSize) {
11 | if (page < 1 || pageSize < 1) {
12 | return DEFAULT_OFFSET;
13 | }
14 |
15 | return (page - 1) * pageSize;
16 | }
17 |
18 | public static int toLimit(int pageSize) {
19 | return pageSize > 0 ? pageSize : DEFAULT_LIMIT;
20 | }
21 |
22 | public static String getLikeParam(String val) {
23 | return "%" + val + "%";
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/com/shzlw/poli/dao/UserFavouriteDao.java:
--------------------------------------------------------------------------------
1 | package com.shzlw.poli.dao;
2 |
3 | import org.springframework.beans.factory.annotation.Autowired;
4 | import org.springframework.jdbc.core.JdbcTemplate;
5 | import org.springframework.stereotype.Repository;
6 |
7 | @Repository
8 | public class UserFavouriteDao {
9 |
10 | @Autowired
11 | JdbcTemplate jt;
12 |
13 | public boolean isFavourite(long userId, long reportId) {
14 | String sql = "SELECT COUNT(1) FROM p_user_favourite WHERE user_id=? AND report_id=?";
15 | int count = jt.queryForObject(sql, new Object[]{ userId, reportId }, Integer.class);
16 | return count != 0;
17 | }
18 |
19 | public int insertFavourite(long userId, long reportId) {
20 | String sql = "INSERT INTO p_user_favourite(user_id, report_id) VALUES(?, ?)";
21 | return jt.update(sql, new Object[]{ userId, reportId });
22 | }
23 |
24 | public int deleteFavourite(long userId, long reportId) {
25 | String sql = "DELETE FROM p_user_favourite WHERE user_id=? AND report_id=?";
26 | return jt.update(sql, new Object[]{ userId, reportId });
27 | }
28 |
29 | public int deleteByUserId(long userId) {
30 | String sql = "DELETE FROM p_user_favourite WHERE user_id=?";
31 | return jt.update(sql, new Object[]{ userId });
32 | }
33 |
34 | public int deleteByReportId(long reportId) {
35 | String sql = "DELETE FROM p_user_favourite WHERE report_id=?";
36 | return jt.update(sql, new Object[]{ reportId });
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/main/java/com/shzlw/poli/dto/AppInfo.java:
--------------------------------------------------------------------------------
1 | package com.shzlw.poli.dto;
2 |
3 | public class AppInfo {
4 |
5 | private String version;
6 | private String localeLanguage;
7 |
8 | public AppInfo(String version, String localeLanguage) {
9 | this.version = version;
10 | this.localeLanguage = localeLanguage;
11 | }
12 |
13 | public String getVersion() {
14 | return version;
15 | }
16 |
17 | public String getLocaleLanguage() {
18 | return localeLanguage;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/com/shzlw/poli/dto/Column.java:
--------------------------------------------------------------------------------
1 | package com.shzlw.poli.dto;
2 |
3 | public class Column {
4 |
5 | private String name;
6 | private String javaType;
7 | private String dbType;
8 | private int length;
9 |
10 | public Column(String name, String javaType, String dbType, int length) {
11 | this.name = name;
12 | this.javaType = javaType;
13 | this.dbType = dbType;
14 | this.length = length;
15 | }
16 |
17 | public String getName() {
18 | return name;
19 | }
20 |
21 | public String getJavaType() {
22 | return javaType;
23 | }
24 |
25 | public String getDbType() {
26 | return dbType;
27 | }
28 |
29 | public int getLength() {
30 | return length;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/com/shzlw/poli/dto/ExportRequest.java:
--------------------------------------------------------------------------------
1 | package com.shzlw.poli.dto;
2 |
3 | public class ExportRequest {
4 |
5 | private long reportId;
6 | private String reportName;
7 | private String sessionKey;
8 | private int width;
9 | private int height;
10 |
11 | public ExportRequest(long reportId, String reportName, String sessionKey, int width, int height) {
12 | this.reportId = reportId;
13 | this.reportName = reportName;
14 | this.sessionKey = sessionKey;
15 | this.width = width;
16 | this.height = height;
17 | }
18 |
19 | public long getReportId() {
20 | return reportId;
21 | }
22 |
23 | public void setReportId(long reportId) {
24 | this.reportId = reportId;
25 | }
26 |
27 | public String getReportName() {
28 | return reportName;
29 | }
30 |
31 | public void setSessionKey(String sessionKey) {
32 | this.sessionKey = sessionKey;
33 | }
34 |
35 | public String getSessionKey() {
36 | return sessionKey;
37 | }
38 |
39 | public int getWidth() {
40 | return width;
41 | }
42 |
43 | public int getHeight() {
44 | return height;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/main/java/com/shzlw/poli/dto/FilterParameter.java:
--------------------------------------------------------------------------------
1 | package com.shzlw.poli.dto;
2 |
3 | import com.fasterxml.jackson.annotation.JsonRawValue;
4 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
5 | import com.shzlw.poli.util.RawStringDeserialzier;
6 |
7 | public class FilterParameter {
8 |
9 | private String type;
10 | private String param;
11 | private String value;
12 | /**
13 | * If the type is: 'slicer' and remark is: 'select all', then no parameters should be applied to the query.
14 | */
15 | private String remark;
16 |
17 | public String getType() {
18 | return type;
19 | }
20 |
21 | public void setType(String type) {
22 | this.type = type;
23 | }
24 |
25 | public String getParam() {
26 | return param;
27 | }
28 |
29 | public void setParam(String param) {
30 | this.param = param;
31 | }
32 |
33 | @JsonRawValue
34 | public String getValue() {
35 | return value;
36 | }
37 |
38 | @JsonDeserialize(using = RawStringDeserialzier.class)
39 | public void setValue(String value) {
40 | this.value = value;
41 | }
42 |
43 | public String getRemark() {
44 | return remark;
45 | }
46 |
47 | public void setRemark(String remark) {
48 | this.remark = remark;
49 | }
50 |
51 | @Override
52 | public String toString() {
53 | return "FilterParameter{" +
54 | "type='" + type + '\'' +
55 | ", param='" + param + '\'' +
56 | ", value='" + value + '\'' +
57 | '}';
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/main/java/com/shzlw/poli/dto/LoginResponse.java:
--------------------------------------------------------------------------------
1 | package com.shzlw.poli.dto;
2 |
3 | import com.fasterxml.jackson.annotation.JsonProperty;
4 |
5 | public class LoginResponse {
6 |
7 | private String username;
8 | private String sysRole;
9 | private String error;
10 |
11 | @JsonProperty(value = "isTempPassword")
12 | private boolean isTempPassword;
13 |
14 | public static LoginResponse ofError(String error) {
15 | return new LoginResponse(error);
16 | }
17 |
18 | public static LoginResponse ofSuccess(String username, String sysRole, boolean isTempPassword) {
19 | return new LoginResponse(username, sysRole, isTempPassword);
20 | }
21 |
22 | private LoginResponse() {};
23 |
24 | private LoginResponse(String error) {
25 | this.error = error;
26 | }
27 |
28 | private LoginResponse(String username, String sysRole, boolean isTempPassword) {
29 | this.username = username;
30 | this.sysRole = sysRole;
31 | this.isTempPassword = isTempPassword;
32 | }
33 |
34 | public String getUsername() {
35 | return username;
36 | }
37 |
38 | public String getSysRole() {
39 | return sysRole;
40 | }
41 |
42 | public String getError() {
43 | return error;
44 | }
45 |
46 | public boolean isTempPassword() {
47 | return isTempPassword;
48 | }
49 |
50 | @Override
51 | public String toString() {
52 | return "LoginResponse{" +
53 | "username='" + username + '\'' +
54 | ", sysRole='" + sysRole + '\'' +
55 | ", error='" + error + '\'' +
56 | ", isTempPassword=" + isTempPassword +
57 | '}';
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/main/java/com/shzlw/poli/dto/QueryRequest.java:
--------------------------------------------------------------------------------
1 | package com.shzlw.poli.dto;
2 |
3 | public class QueryRequest {
4 |
5 | private long jdbcDataSourceId;
6 | private String sqlQuery;
7 | private int resultLimit;
8 |
9 | public QueryRequest() {}
10 |
11 | public long getJdbcDataSourceId() {
12 | return jdbcDataSourceId;
13 | }
14 |
15 | public void setJdbcDataSourceId(long jdbcDataSourceId) {
16 | this.jdbcDataSourceId = jdbcDataSourceId;
17 | }
18 |
19 | public String getSqlQuery() {
20 | return sqlQuery;
21 | }
22 |
23 | public void setSqlQuery(String sqlQuery) {
24 | this.sqlQuery = sqlQuery;
25 | }
26 |
27 | public int getResultLimit() {
28 | return resultLimit;
29 | }
30 |
31 | public void setResultLimit(int resultLimit) {
32 | this.resultLimit = resultLimit;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/com/shzlw/poli/dto/QueryResult.java:
--------------------------------------------------------------------------------
1 | package com.shzlw.poli.dto;
2 |
3 | import com.shzlw.poli.util.Constants;
4 | import org.springframework.util.StringUtils;
5 |
6 | import java.util.ArrayList;
7 | import java.util.List;
8 |
9 | public class QueryResult {
10 |
11 | private List columns = new ArrayList<>();
12 |
13 | /**
14 | * JSON array value
15 | */
16 | private String data;
17 | private String error;
18 |
19 | public static QueryResult ofError(String error) {
20 | return new QueryResult(error);
21 | }
22 |
23 | public static QueryResult ofData(String data, List columns) {
24 | return new QueryResult(data, columns);
25 | }
26 |
27 | private QueryResult() {
28 | }
29 |
30 | private QueryResult(String error) {
31 | this.error = error;
32 | }
33 |
34 | private QueryResult(String data, List columns) {
35 | this.data = data;
36 | this.columns = columns;
37 | }
38 |
39 | public List getColumns() {
40 | return columns;
41 | }
42 |
43 | public String getData() {
44 | if (StringUtils.isEmpty(data)) {
45 | return Constants.EMPTY_JSON_ARRAY;
46 | }
47 | return data;
48 | }
49 |
50 | public String getError() {
51 | return error;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/main/java/com/shzlw/poli/dto/SharedLinkInfo.java:
--------------------------------------------------------------------------------
1 | package com.shzlw.poli.dto;
2 |
3 | import com.shzlw.poli.model.User;
4 |
5 | import java.util.Set;
6 |
7 | public class SharedLinkInfo {
8 |
9 | private User user;
10 | private long reportId;
11 | private Set componentQueryUrls;
12 |
13 | public SharedLinkInfo(User user, long reportId, Set componentQueryUrls) {
14 | this.user = user;
15 | this.reportId = reportId;
16 | this.componentQueryUrls = componentQueryUrls;
17 | }
18 |
19 | public User getUser() {
20 | return user;
21 | }
22 |
23 | public long getReportId() {
24 | return reportId;
25 | }
26 |
27 | public Set getComponentQueryUrls() {
28 | return componentQueryUrls;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/main/java/com/shzlw/poli/dto/SharedReportRow.java:
--------------------------------------------------------------------------------
1 | package com.shzlw.poli.dto;
2 |
3 | import com.shzlw.poli.model.SharedReport;
4 |
5 | public class SharedReportRow extends SharedReport {
6 |
7 | public static final String REPORT_NAME = "report_name";
8 | public static final String CREATED_BY = "created_by";
9 |
10 | private String reportName;
11 | private String createdBy;
12 | private String createDateTime;
13 | private String expirationDate;
14 |
15 | public SharedReportRow() {}
16 |
17 | public String getReportName() {
18 | return reportName;
19 | }
20 |
21 | public void setReportName(String reportName) {
22 | this.reportName = reportName;
23 | }
24 |
25 | public String getCreatedBy() {
26 | return createdBy;
27 | }
28 |
29 | public void setCreatedBy(String createdBy) {
30 | this.createdBy = createdBy;
31 | }
32 |
33 | public String getCreateDateTime() {
34 | return createDateTime;
35 | }
36 |
37 | public void setCreateDateTime(String createDateTime) {
38 | this.createDateTime = createDateTime;
39 | }
40 |
41 | public String getExpirationDate() {
42 | return expirationDate;
43 | }
44 |
45 | public void setExpirationDate(String expirationDate) {
46 | this.expirationDate = expirationDate;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/com/shzlw/poli/dto/Table.java:
--------------------------------------------------------------------------------
1 | package com.shzlw.poli.dto;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 |
6 | public class Table {
7 |
8 | private String name;
9 | private String type;
10 | private List columns = new ArrayList<>();
11 |
12 | public Table(String name, String type) {
13 | this.name = name;
14 | this.type = type;
15 | }
16 |
17 | public String getName() {
18 | return name;
19 | }
20 |
21 | public String getType() {
22 | return type;
23 | }
24 |
25 | public List getColumns() {
26 | return columns;
27 | }
28 |
29 | public void setColumns(List columns) {
30 | this.columns = columns;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/com/shzlw/poli/filter/AuthFilterHelper.java:
--------------------------------------------------------------------------------
1 | package com.shzlw.poli.filter;
2 |
3 | import com.shzlw.poli.util.Constants;
4 |
5 | import java.util.*;
6 |
7 | public final class AuthFilterHelper {
8 |
9 | private AuthFilterHelper() {}
10 |
11 | private static final List VIEWER_GET_PATH = Arrays.asList(
12 | "/ws/reports",
13 | "/ws/cannedreports",
14 | "/ws/components/report/",
15 | "/ws/users/account",
16 | "/ws/sharedreports/generate-sharekey"
17 | );
18 |
19 | private static final List VIEWER_PUT_PATH = Arrays.asList(
20 | "/ws/users/account"
21 | );
22 |
23 | private static final List VIEWER_POST_PATH = Arrays.asList(
24 | "/ws/jdbcquery",
25 | "/ws/cannedreports",
26 | "/ws/reports/favourite",
27 | "/ws/reports/pdf"
28 | );
29 |
30 | private static final List VIEWER_DELETE_PATH = Arrays.asList(
31 | "/ws/cannedreports"
32 | );
33 |
34 | private static final Map> VIEWER_MAP;
35 | static {
36 | Map> viewerTemp = new HashMap<>();
37 | viewerTemp.put(Constants.HTTP_METHOD_GET, VIEWER_GET_PATH);
38 | viewerTemp.put(Constants.HTTP_METHOD_PUT, VIEWER_PUT_PATH);
39 | viewerTemp.put(Constants.HTTP_METHOD_POST, VIEWER_POST_PATH);
40 | viewerTemp.put(Constants.HTTP_METHOD_DELETE, VIEWER_DELETE_PATH);
41 | VIEWER_MAP = Collections.unmodifiableMap(viewerTemp);
42 | }
43 |
44 | private static final List APIKEY_GET_PATH = Arrays.asList(
45 | "/ws/reports",
46 | "/ws/cannedreports",
47 | "/ws/components/report/"
48 | );
49 | private static final List APIKEY_POST_PATH = Arrays.asList(
50 | "/ws/jdbcquery/component"
51 | );
52 |
53 | private static final Map> APIKEY_MAP;
54 | static {
55 | Map> apikeyTemp = new HashMap<>();
56 | apikeyTemp.put(Constants.HTTP_METHOD_GET, APIKEY_GET_PATH);
57 | apikeyTemp.put(Constants.HTTP_METHOD_POST, APIKEY_POST_PATH);
58 | APIKEY_MAP = Collections.unmodifiableMap(apikeyTemp);
59 | }
60 |
61 | public static boolean validateViewer(String requestMethod, String path) {
62 | return isPathStartWith(path, VIEWER_MAP.get(requestMethod));
63 | }
64 |
65 | public static boolean validateByApiKey(String requestMethod, String path) {
66 | return isPathStartWith(path, APIKEY_MAP.get(requestMethod));
67 | }
68 |
69 | private static boolean isPathStartWith(String path, List startWithList) {
70 | if (path == null || startWithList == null) {
71 | return false;
72 | }
73 |
74 | for (String s : startWithList) {
75 | if (path.startsWith(s)) {
76 | return true;
77 | }
78 | }
79 | return false;
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/main/java/com/shzlw/poli/model/AuditLog.java:
--------------------------------------------------------------------------------
1 | package com.shzlw.poli.model;
2 |
3 | public class AuditLog {
4 |
5 | public static final String ID = "id";
6 | public static final String TYPE = "type";
7 | public static final String CREATED_AT = "created_at";
8 | public static final String DATA = "data";
9 |
10 | private long id;
11 | private String createdAt;
12 | private String type;
13 | private String data;
14 |
15 | public AuditLog() {}
16 |
17 | public long getId() {
18 | return id;
19 | }
20 |
21 | public void setId(long id) {
22 | this.id = id;
23 | }
24 |
25 | public String getCreatedAt() {
26 | return createdAt;
27 | }
28 |
29 | public void setCreatedAt(String createdAt) {
30 | this.createdAt = createdAt;
31 | }
32 |
33 | public String getType() {
34 | return type;
35 | }
36 |
37 | public void setType(String type) {
38 | this.type = type;
39 | }
40 |
41 | public String getData() {
42 | return data;
43 | }
44 |
45 | public void setData(String data) {
46 | this.data = data;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/com/shzlw/poli/model/CannedReport.java:
--------------------------------------------------------------------------------
1 | package com.shzlw.poli.model;
2 |
3 | import com.fasterxml.jackson.annotation.JsonRawValue;
4 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
5 | import com.shzlw.poli.util.RawStringDeserialzier;
6 |
7 | public class CannedReport {
8 |
9 | public static final String ID = "id";
10 | public static final String NAME = "name";
11 | public static final String CREATED_AT = "created_at";
12 | public static final String CREATED_BY = "created_by";
13 | public static final String USER_ID = "user_id";
14 | public static final String DATA = "data";
15 |
16 | private long id;
17 | private String name;
18 | private String createdBy;
19 | private long createdAt;
20 |
21 | // Json column
22 | private String data;
23 |
24 | public CannedReport() {}
25 |
26 | public long getId() {
27 | return id;
28 | }
29 |
30 | public void setId(long id) {
31 | this.id = id;
32 | }
33 |
34 | public String getName() {
35 | return name;
36 | }
37 |
38 | public void setName(String name) {
39 | this.name = name;
40 | }
41 |
42 | public String getCreatedBy() {
43 | return createdBy;
44 | }
45 |
46 | public void setCreatedBy(String createdBy) {
47 | this.createdBy = createdBy;
48 | }
49 |
50 | public long getCreatedAt() {
51 | return createdAt;
52 | }
53 |
54 | public void setCreatedAt(long createdAt) {
55 | this.createdAt = createdAt;
56 | }
57 |
58 | @JsonRawValue
59 | public String getData() {
60 | return data;
61 | }
62 |
63 | @JsonDeserialize(using = RawStringDeserialzier.class)
64 | public void setData(String data) {
65 | this.data = data;
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/main/java/com/shzlw/poli/model/Group.java:
--------------------------------------------------------------------------------
1 | package com.shzlw.poli.model;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 |
6 | public class Group {
7 |
8 | public static final String ID = "id";
9 | public static final String NAME = "name";
10 |
11 | private long id;
12 | private String name;
13 |
14 | private List groupReports = new ArrayList<>();
15 |
16 | public Group() {}
17 |
18 | public long getId() {
19 | return id;
20 | }
21 |
22 | public void setId(long id) {
23 | this.id = id;
24 | }
25 |
26 | public String getName() {
27 | return name;
28 | }
29 |
30 | public void setName(String name) {
31 | this.name = name;
32 | }
33 |
34 | public List getGroupReports() {
35 | return groupReports;
36 | }
37 |
38 | public void setGroupReports(List groupReports) {
39 | this.groupReports = groupReports;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/main/java/com/shzlw/poli/model/JdbcDataSource.java:
--------------------------------------------------------------------------------
1 | package com.shzlw.poli.model;
2 |
3 | import java.util.Objects;
4 |
5 | public class JdbcDataSource {
6 |
7 | public static final String ID = "id";
8 | public static final String NAME = "name";
9 | public static final String USERNAME = "username";
10 | public static final String PASSWORD = "password";
11 | public static final String CONNECTION_URL = "connection_url";
12 | public static final String DRIVER_CLASS_NAME = "driver_class_name";
13 | public static final String PING = "ping";
14 |
15 | private long id;
16 | private String name;
17 | private String connectionUrl;
18 | private String driverClassName;
19 | private String username;
20 | private String password;
21 | private String ping;
22 |
23 | public JdbcDataSource() {}
24 |
25 | public long getId() {
26 | return id;
27 | }
28 |
29 | public void setId(long id) {
30 | this.id = id;
31 | }
32 |
33 | public String getName() {
34 | return name;
35 | }
36 |
37 | public void setName(String name) {
38 | this.name = name;
39 | }
40 |
41 | public String getConnectionUrl() {
42 | return connectionUrl;
43 | }
44 |
45 | public void setConnectionUrl(String connectionUrl) {
46 | this.connectionUrl = connectionUrl;
47 | }
48 |
49 | public String getDriverClassName() {
50 | return driverClassName;
51 | }
52 |
53 | public void setDriverClassName(String driverClassName) {
54 | this.driverClassName = driverClassName;
55 | }
56 |
57 | public String getUsername() {
58 | return username;
59 | }
60 |
61 | public void setUsername(String username) {
62 | this.username = username;
63 | }
64 |
65 | public String getPassword() {
66 | return password;
67 | }
68 |
69 | public void setPassword(String password) {
70 | this.password = password;
71 | }
72 |
73 | public String getPing() {
74 | return ping;
75 | }
76 |
77 | public void setPing(String ping) {
78 | this.ping = ping;
79 | }
80 |
81 | @Override
82 | public boolean equals(Object o) {
83 | if (this == o) return true;
84 | if (o == null || getClass() != o.getClass()) return false;
85 | JdbcDataSource that = (JdbcDataSource) o;
86 | return id == that.id &&
87 | Objects.equals(name, that.name) &&
88 | Objects.equals(connectionUrl, that.connectionUrl) &&
89 | Objects.equals(driverClassName, that.driverClassName);
90 | }
91 |
92 | @Override
93 | public int hashCode() {
94 | return Objects.hash(id, name, connectionUrl, driverClassName);
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/main/java/com/shzlw/poli/model/Report.java:
--------------------------------------------------------------------------------
1 | package com.shzlw.poli.model;
2 |
3 | import com.fasterxml.jackson.annotation.JsonProperty;
4 | import com.fasterxml.jackson.annotation.JsonRawValue;
5 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
6 | import com.shzlw.poli.util.RawStringDeserialzier;
7 |
8 | import java.util.Objects;
9 |
10 | public class Report {
11 |
12 | public static final String TABLE = "p_report";
13 | public static final String ID = "id";
14 | public static final String NAME = "name";
15 | public static final String STYLE = "style";
16 | public static final String PROJECT = "project";
17 |
18 | private long id;
19 | private String name;
20 |
21 | /**
22 | * Json field
23 | *
24 | * {
25 | * height: 600,
26 | * backgroundColor: '#123456'
27 | * }
28 | */
29 | private String style;
30 |
31 | private String project;
32 |
33 | @JsonProperty(value = "isFavourite")
34 | private boolean isFavourite;
35 |
36 | public long getId() {
37 | return id;
38 | }
39 |
40 | public void setId(long id) {
41 | this.id = id;
42 | }
43 |
44 | public String getName() {
45 | return name;
46 | }
47 |
48 | public void setName(String name) {
49 | this.name = name;
50 | }
51 |
52 | public String getProject() {
53 | return project;
54 | }
55 |
56 | public void setProject(String project) {
57 | this.project = project;
58 | }
59 |
60 | public boolean isFavourite() {
61 | return isFavourite;
62 | }
63 |
64 | public void setFavourite(boolean favourite) {
65 | isFavourite = favourite;
66 | }
67 |
68 | @JsonRawValue
69 | public String getStyle() {
70 | return style;
71 | }
72 |
73 | @JsonDeserialize(using = RawStringDeserialzier.class)
74 | public void setStyle(String style) {
75 | this.style = style;
76 | }
77 |
78 | @Override
79 | public boolean equals(Object o) {
80 | if (this == o) return true;
81 | if (o == null || getClass() != o.getClass()) return false;
82 | Report report = (Report) o;
83 | return id == report.id &&
84 | name.equals(report.name);
85 | }
86 |
87 | @Override
88 | public int hashCode() {
89 | return Objects.hash(id, name);
90 | }
91 |
92 | @Override
93 | public String toString() {
94 | return "Report{" +
95 | "id=" + id +
96 | ", name='" + name + '\'' +
97 | ", style='" + style + '\'' +
98 | '}';
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/src/main/java/com/shzlw/poli/model/SavedQuery.java:
--------------------------------------------------------------------------------
1 | package com.shzlw.poli.model;
2 |
3 | public class SavedQuery {
4 |
5 | public static final String ID = "id";
6 | public static final String DATASOURCE_ID = "datasource_id";
7 | public static final String NAME = "name";
8 | public static final String SQL_QUERY = "sql_query";
9 | public static final String ENDPOINT_NAME = "endpoint_name";
10 | public static final String ENDPOINT_ACCESSCODE = "endpoint_accesscode";
11 |
12 | private long id;
13 | private String name;
14 | private String sqlQuery;
15 | private long jdbcDataSourceId;
16 |
17 | // Http endpoint
18 | private String endpointName;
19 | private String endpointAccessCode;
20 |
21 | public SavedQuery() {}
22 |
23 | public long getId() {
24 | return id;
25 | }
26 |
27 | public void setId(long id) {
28 | this.id = id;
29 | }
30 |
31 | public String getName() {
32 | return name;
33 | }
34 |
35 | public void setName(String name) {
36 | this.name = name;
37 | }
38 |
39 | public String getSqlQuery() {
40 | return sqlQuery;
41 | }
42 |
43 | public void setSqlQuery(String sqlQuery) {
44 | this.sqlQuery = sqlQuery;
45 | }
46 |
47 | public long getJdbcDataSourceId() {
48 | return jdbcDataSourceId;
49 | }
50 |
51 | public void setJdbcDataSourceId(long jdbcDataSourceId) {
52 | this.jdbcDataSourceId = jdbcDataSourceId;
53 | }
54 |
55 | public String getEndpointName() {
56 | return endpointName;
57 | }
58 |
59 | public void setEndpointName(String endpointName) {
60 | this.endpointName = endpointName;
61 | }
62 |
63 | public String getEndpointAccessCode() {
64 | return endpointAccessCode;
65 | }
66 |
67 | public void setEndpointAccessCode(String endpointAccessCode) {
68 | this.endpointAccessCode = endpointAccessCode;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/main/java/com/shzlw/poli/model/SharedReport.java:
--------------------------------------------------------------------------------
1 | package com.shzlw.poli.model;
2 |
3 | public class SharedReport {
4 |
5 | public static final String ID = "id";
6 | public static final String SHARE_KEY = "share_key";
7 | public static final String REPORT_ID = "report_id";
8 | public static final String REPORT_TYPE = "report_type";
9 | public static final String USER_ID = "user_id";
10 | public static final String CREATED_AT = "created_at";
11 | public static final String EXPIRED_BY = "expired_by";
12 |
13 | private long id;
14 | private String shareKey;
15 | private long reportId;
16 | private String reportType;
17 | private long userId;
18 | private long createdAt;
19 | private long expiredBy;
20 |
21 | public SharedReport() {}
22 |
23 | public long getId() {
24 | return id;
25 | }
26 |
27 | public void setId(long id) {
28 | this.id = id;
29 | }
30 |
31 | public String getShareKey() {
32 | return shareKey;
33 | }
34 |
35 | public void setShareKey(String shareKey) {
36 | this.shareKey = shareKey;
37 | }
38 |
39 | public long getReportId() {
40 | return reportId;
41 | }
42 |
43 | public void setReportId(long reportId) {
44 | this.reportId = reportId;
45 | }
46 |
47 | public String getReportType() {
48 | return reportType;
49 | }
50 |
51 | public void setReportType(String reportType) {
52 | this.reportType = reportType;
53 | }
54 |
55 | public long getUserId() {
56 | return userId;
57 | }
58 |
59 | public void setUserId(long userId) {
60 | this.userId = userId;
61 | }
62 |
63 | public long getCreatedAt() {
64 | return createdAt;
65 | }
66 |
67 | public void setCreatedAt(long createdAt) {
68 | this.createdAt = createdAt;
69 | }
70 |
71 | public long getExpiredBy() {
72 | return expiredBy;
73 | }
74 |
75 | public void setExpiredBy(long expiredBy) {
76 | this.expiredBy = expiredBy;
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/main/java/com/shzlw/poli/model/UserAttribute.java:
--------------------------------------------------------------------------------
1 | package com.shzlw.poli.model;
2 |
3 | public class UserAttribute {
4 |
5 | public static final String ATTR_KEY = "attr_key";
6 | public static final String ATTR_VALUE = "attr_value";
7 |
8 | private String attrKey;
9 | private String attrValue;
10 |
11 | public UserAttribute() {}
12 |
13 | public String getAttrKey() {
14 | return attrKey;
15 | }
16 |
17 | public void setAttrKey(String attrKey) {
18 | this.attrKey = attrKey;
19 | }
20 |
21 | public String getAttrValue() {
22 | return attrValue;
23 | }
24 |
25 | public void setAttrValue(String attrValue) {
26 | this.attrValue = attrValue;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/com/shzlw/poli/rest/AuditLogWs.java:
--------------------------------------------------------------------------------
1 | package com.shzlw.poli.rest;
2 |
3 | import com.shzlw.poli.dao.AuditLogDao;
4 | import com.shzlw.poli.model.AuditLog;
5 | import org.springframework.beans.factory.annotation.Autowired;
6 | import org.springframework.http.MediaType;
7 | import org.springframework.transaction.annotation.Transactional;
8 | import org.springframework.web.bind.annotation.RequestMapping;
9 | import org.springframework.web.bind.annotation.RequestMethod;
10 | import org.springframework.web.bind.annotation.RequestParam;
11 | import org.springframework.web.bind.annotation.RestController;
12 |
13 | import java.util.List;
14 |
15 | @RestController
16 | @RequestMapping("/ws/audit-logs")
17 | public class AuditLogWs {
18 |
19 | @Autowired
20 | AuditLogDao auditLogDao;
21 |
22 | @RequestMapping(method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
23 | @Transactional(readOnly = true)
24 | public List findAll(@RequestParam(name = "search", defaultValue = "", required = false) String searchValue,
25 | @RequestParam(name = "page", defaultValue = "1", required = false) int page,
26 | @RequestParam(name = "size", defaultValue = "20", required = false) int pageSize) {
27 | return auditLogDao.findAll(page, pageSize, searchValue);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/java/com/shzlw/poli/rest/CannedReportWs.java:
--------------------------------------------------------------------------------
1 | package com.shzlw.poli.rest;
2 |
3 | import com.shzlw.poli.dao.CannedReportDao;
4 | import com.shzlw.poli.model.CannedReport;
5 | import com.shzlw.poli.model.User;
6 | import com.shzlw.poli.util.CommonUtils;
7 | import com.shzlw.poli.util.Constants;
8 | import org.springframework.beans.factory.annotation.Autowired;
9 | import org.springframework.http.HttpStatus;
10 | import org.springframework.http.MediaType;
11 | import org.springframework.http.ResponseEntity;
12 | import org.springframework.transaction.annotation.Transactional;
13 | import org.springframework.web.bind.annotation.*;
14 |
15 | import javax.servlet.http.HttpServletRequest;
16 | import java.time.LocalDateTime;
17 | import java.util.List;
18 |
19 | @RestController
20 | @RequestMapping("/ws/cannedreports")
21 | public class CannedReportWs {
22 |
23 | @Autowired
24 | CannedReportDao cannedReportDao;
25 |
26 | @RequestMapping(
27 | value = "/{id}",
28 | method = RequestMethod.GET,
29 | produces = MediaType.APPLICATION_JSON_VALUE)
30 | @Transactional(readOnly = true)
31 | public CannedReport one(@PathVariable("id") long id) {
32 | return cannedReportDao.findById(id);
33 | }
34 |
35 | @RequestMapping(
36 | method = RequestMethod.GET,
37 | produces = MediaType.APPLICATION_JSON_VALUE)
38 | @Transactional(readOnly = true)
39 | public List all() {
40 | return cannedReportDao.findAll();
41 | }
42 |
43 | @RequestMapping(
44 | value = "/myreport",
45 | method = RequestMethod.GET,
46 | produces = MediaType.APPLICATION_JSON_VALUE)
47 | @Transactional(readOnly = true)
48 | public List getMyReport(HttpServletRequest request) {
49 | User user = (User) request.getAttribute(Constants.HTTP_REQUEST_ATTR_USER);
50 | return cannedReportDao.findByUserId(user.getId());
51 | }
52 |
53 | @RequestMapping(method = RequestMethod.POST)
54 | @Transactional
55 | public ResponseEntity add(@RequestBody CannedReport cannedReport,
56 | HttpServletRequest request) {
57 | User user = (User) request.getAttribute(Constants.HTTP_REQUEST_ATTR_USER);
58 | long id = cannedReportDao.insert(user.getId(),
59 | CommonUtils.toEpoch(LocalDateTime.now()),
60 | cannedReport.getName(),
61 | cannedReport.getData());
62 | return new ResponseEntity(id, HttpStatus.CREATED);
63 | }
64 |
65 | @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
66 | @Transactional
67 | public ResponseEntity> delete(@PathVariable("id") long id) {
68 | cannedReportDao.delete(id);
69 | return new ResponseEntity(HttpStatus.NO_CONTENT);
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/main/java/com/shzlw/poli/rest/InfoWs.java:
--------------------------------------------------------------------------------
1 | package com.shzlw.poli.rest;
2 |
3 | import com.shzlw.poli.config.AppProperties;
4 | import com.shzlw.poli.dto.AppInfo;
5 | import com.shzlw.poli.util.Constants;
6 | import org.springframework.beans.factory.annotation.Autowired;
7 | import org.springframework.web.bind.annotation.RequestMapping;
8 | import org.springframework.web.bind.annotation.RequestMethod;
9 | import org.springframework.web.bind.annotation.RestController;
10 |
11 | @RestController
12 | @RequestMapping("/info")
13 | public class InfoWs {
14 |
15 | @Autowired
16 | AppProperties appProperties;
17 |
18 | @RequestMapping(value="/general", method = RequestMethod.GET)
19 | public AppInfo getGeneral() {
20 | return new AppInfo(Constants.CURRENT_VERSION, appProperties.getLocaleLanguage());
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/com/shzlw/poli/rest/SavedQueryWs.java:
--------------------------------------------------------------------------------
1 | package com.shzlw.poli.rest;
2 |
3 | import com.shzlw.poli.dao.SavedQueryDao;
4 | import com.shzlw.poli.model.SavedQuery;
5 | import org.springframework.beans.factory.annotation.Autowired;
6 | import org.springframework.http.HttpStatus;
7 | import org.springframework.http.MediaType;
8 | import org.springframework.http.ResponseEntity;
9 | import org.springframework.transaction.annotation.Transactional;
10 | import org.springframework.util.StringUtils;
11 | import org.springframework.web.bind.annotation.*;
12 |
13 | import java.util.List;
14 |
15 | @RestController
16 | @RequestMapping("/ws/saved-queries")
17 | public class SavedQueryWs {
18 |
19 | @Autowired
20 | SavedQueryDao savedQueryDao;
21 |
22 | @RequestMapping(method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
23 | @Transactional(readOnly = true)
24 | public List findAll() {
25 | return savedQueryDao.findAll();
26 | }
27 |
28 | @RequestMapping(value = "/{id}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
29 | @Transactional(readOnly = true)
30 | public SavedQuery one(@PathVariable("id") long id) {
31 | return savedQueryDao.find(id);
32 | }
33 |
34 | @RequestMapping(method = RequestMethod.POST)
35 | @Transactional
36 | public ResponseEntity add(@RequestBody SavedQuery savedQuery) {
37 | if (StringUtils.isEmpty(savedQuery.getEndpointName())) {
38 | savedQuery.setEndpointName(null);
39 | }
40 | long id = savedQueryDao.insert(savedQuery);
41 | return new ResponseEntity<>(id, HttpStatus.CREATED);
42 | }
43 |
44 | @RequestMapping(method = RequestMethod.PUT)
45 | @Transactional
46 | public ResponseEntity> update(@RequestBody SavedQuery savedQuery) {
47 | if (StringUtils.isEmpty(savedQuery.getEndpointName())) {
48 | savedQuery.setEndpointName(null);
49 | }
50 | savedQueryDao.update(savedQuery);
51 | return new ResponseEntity(HttpStatus.OK);
52 | }
53 |
54 | @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
55 | @Transactional
56 | public ResponseEntity> delete(@PathVariable("id") long id) {
57 | savedQueryDao.delete(id);
58 | return new ResponseEntity<>(HttpStatus.NO_CONTENT);
59 | }
60 |
61 | public List findAllByPage(@RequestParam(name = "search", defaultValue = "", required = false) String searchValue,
62 | @RequestParam(name = "page", defaultValue = "1", required = false) int page,
63 | @RequestParam(name = "size", defaultValue = "20", required = false) int pageSize) {
64 | return savedQueryDao.findAllByPage(page, pageSize, searchValue);
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/main/java/com/shzlw/poli/rest/SharedReportWs.java:
--------------------------------------------------------------------------------
1 | package com.shzlw.poli.rest;
2 |
3 | import com.shzlw.poli.dao.SharedReportDao;
4 | import com.shzlw.poli.dto.SharedReportRow;
5 | import com.shzlw.poli.model.SharedReport;
6 | import com.shzlw.poli.model.User;
7 | import com.shzlw.poli.service.SharedReportService;
8 | import com.shzlw.poli.util.CommonUtils;
9 | import com.shzlw.poli.util.Constants;
10 | import com.shzlw.poli.util.PasswordUtils;
11 | import org.springframework.beans.factory.annotation.Autowired;
12 | import org.springframework.http.HttpStatus;
13 | import org.springframework.http.ResponseEntity;
14 | import org.springframework.transaction.annotation.Transactional;
15 | import org.springframework.web.bind.annotation.*;
16 |
17 | import javax.servlet.http.HttpServletRequest;
18 | import java.time.LocalDateTime;
19 | import java.util.List;
20 |
21 | @RestController
22 | @RequestMapping("/ws/sharedreports")
23 | public class SharedReportWs {
24 |
25 | @Autowired
26 | SharedReportDao sharedReportDao;
27 |
28 | @Autowired
29 | SharedReportService sharedReportService;
30 |
31 | @RequestMapping(value="/generate-sharekey", method = RequestMethod.POST)
32 | @Transactional
33 | public String generateSharedReportUrl(@RequestBody SharedReport sharedReport,
34 | HttpServletRequest request) {
35 | // Try reuse the same share key if it's the same report and has the same expiration date.
36 | List sharedReports = sharedReportDao.findByReportId(sharedReport.getReportId());
37 | String newExpirationDate = CommonUtils.toReadableDateTime(CommonUtils.fromEpoch(sharedReport.getExpiredBy()));
38 | for (SharedReport sr : sharedReports) {
39 | String expirationDate = CommonUtils.toReadableDateTime(CommonUtils.fromEpoch(sr.getExpiredBy()));
40 | if (newExpirationDate.equals(expirationDate)) {
41 | return sr.getShareKey();
42 | }
43 | }
44 |
45 | // Create a new shared report.
46 | User user = (User) request.getAttribute(Constants.HTTP_REQUEST_ATTR_USER);
47 | String shareKey = Constants.SHARE_KEY_PREFIX + PasswordUtils.getUniqueId();
48 | long createdAt = CommonUtils.toEpoch(LocalDateTime.now());
49 | sharedReportDao.insert(shareKey, sharedReport.getReportId(), sharedReport.getReportType(),
50 | user.getId(), createdAt, sharedReport.getExpiredBy());
51 | return shareKey;
52 | }
53 |
54 | @RequestMapping(method = RequestMethod.GET)
55 | @Transactional(readOnly = true)
56 | public List findAllSharedReports() {
57 | return sharedReportDao.findAll();
58 | }
59 |
60 | @RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
61 | @Transactional
62 | public ResponseEntity> delete(@PathVariable("id") long id) {
63 | SharedReport sharedReport = sharedReportDao.findById(id);
64 | sharedReportService.invalidateSharedLinkInfoCacheByShareKey(sharedReport.getShareKey());
65 | sharedReportDao.delete(id);
66 | return new ResponseEntity(HttpStatus.NO_CONTENT);
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/main/java/com/shzlw/poli/rest/api/SavedQueryEndpointWs.java:
--------------------------------------------------------------------------------
1 | package com.shzlw.poli.rest.api;
2 |
3 | import com.shzlw.poli.dao.SavedQueryDao;
4 | import com.shzlw.poli.dto.QueryResult;
5 | import com.shzlw.poli.model.SavedQuery;
6 | import com.shzlw.poli.service.AuditLogService;
7 | import com.shzlw.poli.service.JdbcDataSourceService;
8 | import com.shzlw.poli.service.JdbcQueryService;
9 | import org.springframework.beans.factory.annotation.Autowired;
10 | import org.springframework.http.HttpStatus;
11 | import org.springframework.http.ResponseEntity;
12 | import org.springframework.transaction.annotation.Transactional;
13 | import org.springframework.util.StringUtils;
14 | import org.springframework.web.bind.annotation.RequestMapping;
15 | import org.springframework.web.bind.annotation.RequestMethod;
16 | import org.springframework.web.bind.annotation.RequestParam;
17 | import org.springframework.web.bind.annotation.RestController;
18 |
19 | import javax.servlet.http.HttpServletRequest;
20 | import javax.sql.DataSource;
21 |
22 | @RestController
23 | @RequestMapping("/api/v1")
24 | public class SavedQueryEndpointWs {
25 |
26 | @Autowired
27 | SavedQueryDao savedQueryDao;
28 |
29 | @Autowired
30 | JdbcDataSourceService jdbcDataSourceService;
31 |
32 | @Autowired
33 | JdbcQueryService jdbcQueryService;
34 |
35 | @Autowired
36 | AuditLogService auditLogService;
37 |
38 | @RequestMapping(value = "/saved-queries", method = RequestMethod.GET)
39 | @Transactional
40 | public ResponseEntity getSavedQuery(@RequestParam("name") String name,
41 | @RequestParam(name = "accessCode", required = false, defaultValue = "") String accessCode,
42 | @RequestParam(name = "contentType", required = false, defaultValue = "") String contentType,
43 | HttpServletRequest request) {
44 |
45 | auditLogService.logQueryEndpointAccess(request, name);
46 |
47 | SavedQuery savedQuery = savedQueryDao.findByEndpointName(name);
48 | if (savedQuery == null) {
49 | return new ResponseEntity<>(null, HttpStatus.NOT_FOUND);
50 | }
51 |
52 | // Validate access code
53 | String endpointAccessCode = savedQuery.getEndpointAccessCode();
54 | if (!StringUtils.isEmpty(endpointAccessCode) && !endpointAccessCode.equals(accessCode)) {
55 | return new ResponseEntity<>(null, HttpStatus.UNAUTHORIZED);
56 | }
57 |
58 | DataSource dataSource = jdbcDataSourceService.getDataSource(savedQuery.getJdbcDataSourceId());
59 | if (dataSource == null) {
60 | return new ResponseEntity<>(null, HttpStatus.NOT_FOUND);
61 | }
62 |
63 | QueryResult queryResult = jdbcQueryService.executeQuery(dataSource, savedQuery.getSqlQuery(), contentType);
64 | if (queryResult.getError() != null) {
65 | return new ResponseEntity<>(queryResult.getError(), HttpStatus.BAD_REQUEST);
66 | } else {
67 | return new ResponseEntity<>(queryResult.getData(), HttpStatus.OK);
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/main/java/com/shzlw/poli/service/AuditLogService.java:
--------------------------------------------------------------------------------
1 | package com.shzlw.poli.service;
2 |
3 | import com.shzlw.poli.dao.AuditLogDao;
4 | import com.shzlw.poli.util.CommonUtils;
5 | import com.shzlw.poli.util.HttpUtils;
6 | import org.springframework.beans.factory.annotation.Autowired;
7 | import org.springframework.stereotype.Service;
8 |
9 | import javax.servlet.http.HttpServletRequest;
10 | import java.time.LocalDateTime;
11 |
12 | @Service
13 | public class AuditLogService {
14 |
15 | @Autowired
16 | AuditLogDao auditLogDao;
17 |
18 | public void logQueryEndpointAccess(HttpServletRequest request, String name) {
19 | long now = CommonUtils.toEpoch(LocalDateTime.now());
20 | String ip = HttpUtils.getIpAddress(request);
21 | String ipAddress = ip == null ? "" : ip;
22 | String type = "query endpoint";
23 | String logData = String.format("%s accesses query endpoint: %s", ipAddress, name);
24 | auditLogDao.insert(now, type, logData);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/com/shzlw/poli/service/HttpClient.java:
--------------------------------------------------------------------------------
1 | package com.shzlw.poli.service;
2 |
3 | import okhttp3.*;
4 | import org.springframework.beans.factory.annotation.Autowired;
5 | import org.springframework.stereotype.Service;
6 |
7 | import java.io.IOException;
8 |
9 | @Service
10 | public class HttpClient {
11 |
12 | private static final MediaType MEDIA_TYPE_PLAINTEXT = MediaType.parse("text/plain; charset=utf-8");
13 | private static final MediaType MEDIA_TYPE_JSON = MediaType.parse("application/json; charset=utf-8");
14 |
15 | @Autowired
16 | OkHttpClient okHttpClient;
17 |
18 | public byte[] get(String url) throws IOException {
19 | Request request = new Request.Builder()
20 | .url(url)
21 | .build();
22 | try (Response response = okHttpClient.newCall(request).execute()) {
23 | return response.body().bytes();
24 | }
25 | }
26 |
27 | public byte[] postJson(String url, String data) throws IOException {
28 | RequestBody body = RequestBody.create(data, MEDIA_TYPE_JSON);
29 | Request request = new Request.Builder()
30 | .url(url)
31 | .post(body)
32 | .build();
33 | try (Response response = okHttpClient.newCall(request).execute()) {
34 | return response.body().bytes();
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/com/shzlw/poli/service/JdbcDataSourceService.java:
--------------------------------------------------------------------------------
1 | package com.shzlw.poli.service;
2 |
3 | import com.google.common.cache.Cache;
4 | import com.google.common.cache.CacheBuilder;
5 | import com.google.common.cache.CacheLoader;
6 | import com.google.common.cache.RemovalListener;
7 | import com.shzlw.poli.config.AppProperties;
8 | import com.shzlw.poli.dao.JdbcDataSourceDao;
9 | import com.shzlw.poli.model.JdbcDataSource;
10 | import com.zaxxer.hikari.HikariDataSource;
11 | import org.springframework.beans.factory.annotation.Autowired;
12 | import org.springframework.stereotype.Service;
13 | import org.springframework.util.StringUtils;
14 |
15 | import javax.annotation.PostConstruct;
16 | import javax.annotation.PreDestroy;
17 | import javax.sql.DataSource;
18 | import java.util.concurrent.ExecutionException;
19 | import java.util.concurrent.TimeUnit;
20 |
21 | @Service
22 | public class JdbcDataSourceService {
23 |
24 | // 10 seconds
25 | private static final int LEAK_DETECTION_THRESHOLD = 10000;
26 |
27 | /**
28 | * Key: JdbcDataSource id
29 | * Value: HikariDataSource
30 | */
31 | private static final Cache DATA_SOURCE_CACHE = CacheBuilder.newBuilder()
32 | .expireAfterWrite(5, TimeUnit.MINUTES)
33 | .removalListener((RemovalListener) removal -> {
34 | HikariDataSource ds = removal.getValue();
35 | ds.close();
36 | })
37 | .build();
38 |
39 | @Autowired
40 | JdbcDataSourceDao jdbcDataSourceDao;
41 |
42 | @Autowired
43 | AppProperties appProperties;
44 |
45 | @PostConstruct
46 | public void init() {
47 | }
48 |
49 | @PreDestroy
50 | public void shutdown() {
51 | for (HikariDataSource hiDs : DATA_SOURCE_CACHE.asMap().values()) {
52 | hiDs.close();
53 | }
54 | }
55 |
56 | public void removeFromCache(long dataSourceId) {
57 | DATA_SOURCE_CACHE.invalidate(dataSourceId);
58 | }
59 |
60 | public DataSource getDataSource(long dataSourceId) {
61 | if (dataSourceId == 0) {
62 | return null;
63 | }
64 |
65 | try {
66 | DataSource hiDs = DATA_SOURCE_CACHE.get(dataSourceId, () -> {
67 | JdbcDataSource dataSource = jdbcDataSourceDao.findById(dataSourceId);
68 | if (dataSource == null) {
69 | return null;
70 | }
71 | HikariDataSource newHiDs = new HikariDataSource();
72 | newHiDs.setJdbcUrl(dataSource.getConnectionUrl());
73 | newHiDs.setUsername(dataSource.getUsername());
74 | newHiDs.setPassword(dataSource.getPassword());
75 | if (!StringUtils.isEmpty(dataSource.getDriverClassName())) {
76 | newHiDs.setDriverClassName(dataSource.getDriverClassName());
77 | }
78 | newHiDs.setMaximumPoolSize(appProperties.getDatasourceMaximumPoolSize());
79 | newHiDs.setLeakDetectionThreshold(LEAK_DETECTION_THRESHOLD);
80 | return newHiDs;
81 | });
82 | return hiDs;
83 | } catch (ExecutionException | CacheLoader.InvalidCacheLoadException e) {
84 | return null;
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/main/java/com/shzlw/poli/service/ReportService.java:
--------------------------------------------------------------------------------
1 | package com.shzlw.poli.service;
2 |
3 | import com.google.common.cache.Cache;
4 | import com.google.common.cache.CacheBuilder;
5 | import com.google.common.cache.CacheLoader;
6 | import com.shzlw.poli.dao.ReportDao;
7 | import com.shzlw.poli.model.Report;
8 | import com.shzlw.poli.model.User;
9 | import com.shzlw.poli.util.Constants;
10 | import org.slf4j.Logger;
11 | import org.slf4j.LoggerFactory;
12 | import org.springframework.beans.factory.annotation.Autowired;
13 | import org.springframework.stereotype.Service;
14 | import org.springframework.util.StringUtils;
15 |
16 | import java.util.ArrayList;
17 | import java.util.Collections;
18 | import java.util.List;
19 | import java.util.concurrent.ExecutionException;
20 | import java.util.concurrent.TimeUnit;
21 |
22 | @Service
23 | public class ReportService {
24 |
25 | private static final Logger LOGGER = LoggerFactory.getLogger(ReportService.class);
26 |
27 | /**
28 | * Key: User id
29 | * Value: Report
30 | */
31 | private static final Cache> USER_REPORT_CACHE = CacheBuilder.newBuilder()
32 | .expireAfterWrite(5, TimeUnit.MINUTES)
33 | .build();
34 |
35 | @Autowired
36 | ReportDao reportDao;
37 |
38 | public List getReportsByUser(User user) {
39 | if (StringUtils.isEmpty(user)) {
40 | return Collections.emptyList();
41 | }
42 |
43 | try {
44 | List rt = USER_REPORT_CACHE.get(user.getId(), () -> {
45 | List reports = new ArrayList<>();
46 | if (Constants.SYS_ROLE_VIEWER.equals(user.getSysRole())) {
47 | reports = reportDao.findByViewer(user.getId());
48 | } else {
49 | reports = reportDao.findAll();
50 | }
51 |
52 | return reports;
53 | });
54 | return rt;
55 | } catch (ExecutionException | CacheLoader.InvalidCacheLoadException e) {
56 | return Collections.emptyList();
57 | }
58 | }
59 |
60 | public void invalidateCache(long userId) {
61 | USER_REPORT_CACHE.invalidate(userId);
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/main/java/com/shzlw/poli/service/UserService.java:
--------------------------------------------------------------------------------
1 | package com.shzlw.poli.service;
2 |
3 | import com.google.common.cache.Cache;
4 | import com.google.common.cache.CacheBuilder;
5 | import com.google.common.cache.CacheLoader;
6 | import com.shzlw.poli.dao.UserDao;
7 | import com.shzlw.poli.model.User;
8 | import org.slf4j.Logger;
9 | import org.slf4j.LoggerFactory;
10 | import org.springframework.beans.factory.annotation.Autowired;
11 | import org.springframework.stereotype.Service;
12 | import org.springframework.util.StringUtils;
13 |
14 | import java.util.concurrent.ExecutionException;
15 | import java.util.concurrent.TimeUnit;
16 |
17 | @Service
18 | public class UserService {
19 |
20 | private static final Logger LOGGER = LoggerFactory.getLogger(UserService.class);
21 | /**
22 | * Key: Session key
23 | * Value: User
24 | */
25 | private static final Cache SESSION_USER_CACHE = CacheBuilder.newBuilder()
26 | .expireAfterWrite(5, TimeUnit.MINUTES)
27 | .build();
28 |
29 | /**
30 | * Key: Api key
31 | * Value: User
32 | */
33 | private static final Cache API_KEY_USER_CACHE = CacheBuilder.newBuilder()
34 | .expireAfterWrite(5, TimeUnit.MINUTES)
35 | .build();
36 |
37 | @Autowired
38 | UserDao userDao;
39 |
40 | public User getUserBySessionKey(String sessionKey) {
41 | if (StringUtils.isEmpty(sessionKey)) {
42 | return null;
43 | }
44 |
45 | try {
46 | User user = SESSION_USER_CACHE.get(sessionKey, () -> {
47 | User u = userDao.findBySessionKey(sessionKey);
48 | u.setUserAttributes(userDao.findUserAttributes(u.getId()));
49 | return u;
50 | });
51 | return user;
52 | } catch (ExecutionException | CacheLoader.InvalidCacheLoadException e) {
53 | return null;
54 | }
55 | }
56 |
57 | public User getUserByApiKey(String apiKey) {
58 | if (StringUtils.isEmpty(apiKey)) {
59 | return null;
60 | }
61 |
62 | try {
63 | User user = API_KEY_USER_CACHE.get(apiKey, () -> {
64 | User u = userDao.findByApiKey(apiKey);
65 | u.setUserAttributes(userDao.findUserAttributes(u.getId()));
66 | return u;
67 | });
68 | return user;
69 | } catch (ExecutionException | CacheLoader.InvalidCacheLoadException e) {
70 | return null;
71 | }
72 | }
73 |
74 | public void newOrUpdateUser(User user, String oldSessionKey, String newSessionKey) {
75 | invalidateSessionUserCache(oldSessionKey);
76 | SESSION_USER_CACHE.put(newSessionKey, user);
77 | }
78 |
79 | public void invalidateSessionUserCache(String sessionKey) {
80 | if (sessionKey != null) {
81 | SESSION_USER_CACHE.invalidate(sessionKey);
82 | }
83 | }
84 |
85 | public void invalidateApiKeyUserCache(String apiKey) {
86 | if (apiKey != null) {
87 | API_KEY_USER_CACHE.invalidate(apiKey);
88 | }
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/src/main/java/com/shzlw/poli/util/CommonUtils.java:
--------------------------------------------------------------------------------
1 | package com.shzlw.poli.util;
2 |
3 | import org.springframework.lang.Nullable;
4 |
5 | import java.time.Instant;
6 | import java.time.LocalDateTime;
7 | import java.time.ZoneId;
8 | import java.time.format.DateTimeFormatter;
9 |
10 | public final class CommonUtils {
11 |
12 | private CommonUtils() {}
13 |
14 | public static LocalDateTime fromEpoch(long epoch) {
15 | return Instant.ofEpochMilli(epoch).atZone(ZoneId.systemDefault()).toLocalDateTime();
16 | }
17 |
18 | public static long toEpoch(@Nullable LocalDateTime dateTime) {
19 | return dateTime.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
20 | }
21 |
22 | public static String toReadableDateTime(LocalDateTime localDateTime) {
23 | DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
24 | return localDateTime.format(formatter);
25 | }
26 |
27 | public static String toReadableDate(LocalDateTime localDateTime) {
28 | DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
29 | return localDateTime.format(formatter);
30 | }
31 |
32 | public static String getSimpleError(Exception e) {
33 | return "ERROR: " + e.getClass().getCanonicalName() + ": " + e.getMessage();
34 | }
35 |
36 | public static String getParamByAttrKey(String attrKey) {
37 | return "$user_attr[" + attrKey + "]";
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/com/shzlw/poli/util/Constants.java:
--------------------------------------------------------------------------------
1 | package com.shzlw.poli.util;
2 |
3 | public final class Constants {
4 |
5 | private Constants() {}
6 |
7 | public static final String CURRENT_VERSION = "0.12.2";
8 |
9 | public static final String SUCCESS = "success";
10 | public static final String GOOD = "";
11 | public static final String ERROR = "";
12 |
13 | public static final String SESSION_KEY = "pskey";
14 | public static final int COOKIE_TIMEOUT = 8640000;
15 |
16 | public static final String API_KEY_PREFIX = "apk_";
17 | public static final String SESSION_KEY_PREFIX = "sk_";
18 | public static final String SHARE_KEY_PREFIX = "sha_";
19 |
20 | public static final String PASSWORD_SALT = "awesome";
21 |
22 | public static final String ENCRYPT_KEY = "1234salt1234salt";
23 | public static final String ENCRYPT_IV = "5678salt5678salt";
24 |
25 | public static final String FILTER_TYPE_SLICER = "slicer";
26 | public static final String FILTER_TYPE_SINGLE = "single";
27 | public static final String FILTER_TYPE_DATE_PICKER = "date picker";
28 | public static final String FILTER_TYPE_USER_ATTRIBUTE = "user attribute";
29 |
30 | public static final String SLICER_SELECT_ALL = "select all";
31 | public static final String SLICER_SELECT_NONE = "select none";
32 |
33 | public static final String SYS_ROLE_VIEWER = "viewer";
34 | public static final String SYS_ROLE_DEVELOPER = "developer";
35 | public static final String SYS_ROLE_ADMIN = "admin";
36 |
37 | public static final String COMPONENT_TYPE_FILTER = "filter";
38 | public static final String COMPONENT_TYPE_CHART = "chart";
39 | public static final String COMPONENT_TYPE_STATIC = "static";
40 |
41 | public static final String EMPTY_JSON_ARRAY = "[]";
42 | public static final String HTTP_REQUEST_ATTR_USER = "attr_user";
43 |
44 | public static final String HTTP_METHOD_GET = "GET";
45 | public static final String HTTP_METHOD_POST = "POST";
46 | public static final String HTTP_METHOD_PUT = "PUT";
47 | public static final String HTTP_METHOD_DELETE = "DELETE";
48 |
49 | public static final String HTTP_HEADER_API_KEY = "Poli-Api-Key";
50 | public static final String HTTP_HEADER_SHARE_KEY = "Poli-Share-Key";
51 |
52 | public static final String ERROR_NO_DATA_SOURCE_FOUND = "No data source found";
53 | public static final String ERROR_EMPTY_SQL_QUERY = "SQL query cannot be empty";
54 |
55 | public static final int QUERY_RESULT_NOLIMIT = 0;
56 | public static final int MAXIMUM_QUERY_RECORDS_NOLIMIT = -1;
57 |
58 | public static final String CONTENT_TYPE_JSON = "json";
59 | public static final String CONTENT_TYPE_CSV = "csv";
60 | }
61 |
--------------------------------------------------------------------------------
/src/main/java/com/shzlw/poli/util/HttpUtils.java:
--------------------------------------------------------------------------------
1 | package com.shzlw.poli.util;
2 |
3 | import javax.servlet.http.Cookie;
4 | import javax.servlet.http.HttpServletRequest;
5 |
6 | public final class HttpUtils {
7 |
8 | private HttpUtils() {}
9 |
10 | public static String getSessionKey(HttpServletRequest httpRequest) {
11 | Cookie[] cookies = httpRequest.getCookies();
12 | if (cookies == null || cookies.length == 0) {
13 | return null;
14 | }
15 |
16 | for (int i = 0; i < cookies.length; i++) {
17 | String name = cookies[i].getName();
18 | String value = cookies[i].getValue();
19 | if (Constants.SESSION_KEY.equals(name)) {
20 | return value;
21 | }
22 | }
23 | return null;
24 | }
25 |
26 | public static String getIpAddress(HttpServletRequest httpRequest) {
27 | String ipAddress = httpRequest.getHeader("X-FORWARDED-FOR");
28 | if (ipAddress == null) {
29 | ipAddress = httpRequest.getRemoteAddr();
30 | }
31 | return ipAddress;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/com/shzlw/poli/util/RawStringDeserialzier.java:
--------------------------------------------------------------------------------
1 | package com.shzlw.poli.util;
2 |
3 | import com.fasterxml.jackson.core.JsonParser;
4 | import com.fasterxml.jackson.core.TreeNode;
5 | import com.fasterxml.jackson.databind.DeserializationContext;
6 | import com.fasterxml.jackson.databind.JsonDeserializer;
7 |
8 | import java.io.IOException;
9 |
10 | public class RawStringDeserialzier extends JsonDeserializer {
11 |
12 | @Override
13 | public String deserialize(JsonParser parser, DeserializationContext ctx) throws IOException {
14 | TreeNode tree = parser.getCodec().readTree(parser);
15 | return tree.toString();
16 | }
17 | }
18 |
19 |
--------------------------------------------------------------------------------
/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | #server.servlet.contextPath=/poli
2 | server.port=6688
3 | spring.main.banner_mode=off
4 |
5 | spring.datasource.url=jdbc:sqlite:c:/test/poli.db
6 | spring.datasource.driver-class-name=org.sqlite.JDBC
7 |
8 | # datasource extra settings.
9 | spring.datasource.max-active=32
10 | spring.datasource.max-idle=16
11 | spring.datasource.min-idle=8
12 | spring.datasource.test-while-idle= true
13 | spring.datasource.time-between-eviction-runs-millis=60000
14 | spring.datasource.validation-query=SELECT 1
15 | spring.datasource.tomcat.max-wait=10000
16 | spring.datasource.sql-script-encoding=UTF-8
17 |
18 | # JDBC
19 | spring.jdbc.template.fetch-size=100
20 |
21 | poli.datasource-maximum-pool-size=50
22 | poli.maximum-query-records=-1
23 | poli.locale-language=en
24 | poli.allow-multiple-query-statements=false
25 | poli.export-server-url=http://127.0.0.1:6689/pdf
26 |
--------------------------------------------------------------------------------
/src/main/resources/loader.properties:
--------------------------------------------------------------------------------
1 | # loader
2 | loader.path=jdbc-drivers/
--------------------------------------------------------------------------------
/src/main/resources/logback-spring.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | log/server.log
6 |
7 |
8 |
9 | %d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n
10 |
11 |
12 |
13 |
14 | log/server-%d{yyyy-MM-dd}.%i.log
15 | 3MB
16 | 5
17 | 15MB
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/src/test/java/com/shzlw/poli/dao/AuditLogDaoTest.java:
--------------------------------------------------------------------------------
1 | package com.shzlw.poli.dao;
2 |
3 | import com.shzlw.poli.model.AuditLog;
4 | import com.shzlw.poli.util.CommonUtils;
5 | import org.junit.Assert;
6 | import org.junit.Test;
7 | import org.junit.runner.RunWith;
8 | import org.springframework.beans.factory.annotation.Autowired;
9 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
10 | import org.springframework.boot.test.context.SpringBootTest;
11 | import org.springframework.test.context.TestPropertySource;
12 | import org.springframework.test.context.jdbc.Sql;
13 | import org.springframework.test.context.junit4.SpringRunner;
14 |
15 | import java.time.LocalDateTime;
16 | import java.util.List;
17 |
18 | @RunWith(SpringRunner.class)
19 | @SpringBootTest
20 | @AutoConfigureMockMvc
21 | @TestPropertySource(locations="classpath:application-test.properties")
22 | @Sql(scripts = "classpath:schema-sqlite.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
23 | public class AuditLogDaoTest {
24 |
25 | @Autowired
26 | AuditLogDao auditLogDao;
27 |
28 | @Test
29 | public void test() {
30 | long epoch = CommonUtils.toEpoch(LocalDateTime.now());
31 | for (int i = 0; i < 20; i++) {
32 | auditLogDao.insert(epoch, "type", "data" + i);
33 | }
34 |
35 | List list = auditLogDao.findAll(1, 10, "");
36 | Assert.assertEquals(10, list.size());
37 | for (int i = 0; i < 10; i++) {
38 | Assert.assertEquals("data" + (19 - i), list.get(i).getData());
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/src/test/java/com/shzlw/poli/dao/DaoHelperTest.java:
--------------------------------------------------------------------------------
1 | package com.shzlw.poli.dao;
2 |
3 | import org.junit.Assert;
4 | import org.junit.Test;
5 |
6 | public class DaoHelperTest {
7 |
8 | @Test
9 | public void testToOffset() {
10 | Assert.assertEquals(0, DaoHelper.toOffset(1, 10));
11 | Assert.assertEquals(DaoHelper.DEFAULT_OFFSET, DaoHelper.toOffset(0, 10));
12 | Assert.assertEquals(DaoHelper.DEFAULT_OFFSET, DaoHelper.toOffset(1, 0));
13 | Assert.assertEquals(90, DaoHelper.toOffset(10, 10));
14 | }
15 |
16 | @Test
17 | public void testToLimit() {
18 | Assert.assertEquals(10, DaoHelper.toLimit(10));
19 | Assert.assertEquals(DaoHelper.DEFAULT_LIMIT, DaoHelper.toLimit(0));
20 | Assert.assertEquals(DaoHelper.DEFAULT_LIMIT, DaoHelper.toLimit(-1));
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/test/java/com/shzlw/poli/rest/InfoWsTest.java:
--------------------------------------------------------------------------------
1 | package com.shzlw.poli.rest;
2 |
3 | import com.fasterxml.jackson.databind.ObjectMapper;
4 | import com.shzlw.poli.config.AppProperties;
5 | import com.shzlw.poli.dto.AppInfo;
6 | import com.shzlw.poli.util.Constants;
7 | import org.junit.Assert;
8 | import org.junit.Test;
9 | import org.junit.runner.RunWith;
10 | import org.springframework.beans.factory.annotation.Autowired;
11 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
12 | import org.springframework.boot.test.context.SpringBootTest;
13 | import org.springframework.test.context.TestPropertySource;
14 | import org.springframework.test.context.junit4.SpringRunner;
15 | import org.springframework.test.web.servlet.MockMvc;
16 | import org.springframework.test.web.servlet.MvcResult;
17 |
18 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
19 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
20 |
21 | @RunWith(SpringRunner.class)
22 | @SpringBootTest
23 | @AutoConfigureMockMvc
24 | @TestPropertySource(locations="classpath:application-test.properties")
25 | public class InfoWsTest {
26 |
27 | @Autowired
28 | MockMvc mvc;
29 |
30 | @Autowired
31 | ObjectMapper mapper;
32 |
33 | @Autowired
34 | AppProperties appProperties;
35 |
36 | @Test
37 | public void testGetAppInfo() throws Exception {
38 | MvcResult mvcResult = mvc.perform(get("/info/general")).andExpect(status().isOk())
39 | .andReturn();
40 | String responeText = mvcResult.getResponse().getContentAsString();
41 | AppInfo appInfo = mapper.readValue(responeText, AppInfo.class);
42 |
43 | Assert.assertEquals(Constants.CURRENT_VERSION, appInfo.getVersion());
44 | Assert.assertEquals(appProperties.getLocaleLanguage(), appInfo.getLocaleLanguage());
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/test/java/com/shzlw/poli/util/CommonUtilsTest.java:
--------------------------------------------------------------------------------
1 | package com.shzlw.poli.util;
2 |
3 | import org.junit.Assert;
4 | import org.junit.Test;
5 |
6 | import java.time.LocalDateTime;
7 | import java.time.format.DateTimeFormatter;
8 | import java.time.temporal.ChronoUnit;
9 |
10 | public class CommonUtilsTest {
11 |
12 | @Test
13 | public void testFromEpoch() {
14 | Assert.assertNotNull(CommonUtils.fromEpoch(0));
15 | }
16 |
17 | @Test
18 | public void testFromEpoch_toEpoch() {
19 | LocalDateTime dateTime = LocalDateTime.now();
20 | long epoch = CommonUtils.toEpoch(dateTime);
21 | LocalDateTime newDateTime = CommonUtils.fromEpoch(epoch);
22 | long newEpoch = CommonUtils.toEpoch(newDateTime);
23 |
24 | Assert.assertEquals(0, dateTime.truncatedTo(ChronoUnit.SECONDS).compareTo(newDateTime.truncatedTo(ChronoUnit.SECONDS)));
25 | Assert.assertEquals(epoch, newEpoch);
26 | }
27 |
28 | @Test
29 | public void testToReadableDateTime() {
30 | String date = "2019-01-01 01:00:00";
31 | DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:s");
32 | LocalDateTime dateTime = LocalDateTime.parse(date, formatter);
33 | Assert.assertEquals(date, CommonUtils.toReadableDateTime(dateTime));
34 | }
35 |
36 | @Test
37 | public void testToReadableDate() {
38 | String date = "2019-01-01 01:00:00";
39 | DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:s");
40 | LocalDateTime dateTime = LocalDateTime.parse(date, formatter);
41 | Assert.assertEquals("2019-01-01", CommonUtils.toReadableDate(dateTime));
42 | }
43 |
44 | @Test
45 | public void testGetParamByAttrKey() {
46 | String rt = CommonUtils.getParamByAttrKey("attrKey");
47 | Assert.assertEquals("$user_attr[attrKey]", rt);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/test/java/com/shzlw/poli/util/PasswordUtilsTest.java:
--------------------------------------------------------------------------------
1 | package com.shzlw.poli.util;
2 |
3 | import org.junit.Assert;
4 | import org.junit.Test;
5 |
6 | public class PasswordUtilsTest {
7 |
8 | @Test
9 | public void testDecryptEncrypt() {
10 | String value = "1234567890";
11 | String b = PasswordUtils.decrypt(PasswordUtils.encrypt(value));
12 | Assert.assertEquals(value, b);
13 | }
14 |
15 | @Test
16 | public void testEncryptedPassword_2() {
17 | String password = "1234567890";
18 | Assert.assertEquals(password, PasswordUtils.getDecryptedPassword(PasswordUtils.getEncryptedPassword(password)));
19 |
20 | password = "";
21 | Assert.assertEquals(password, PasswordUtils.getDecryptedPassword(PasswordUtils.getEncryptedPassword(password)));
22 |
23 | password = null;
24 | Assert.assertEquals("", PasswordUtils.getDecryptedPassword(PasswordUtils.getEncryptedPassword(password)));
25 | }
26 |
27 | @Test
28 | public void testGetUniqueId() {
29 | Assert.assertEquals(22, PasswordUtils.getUniqueId().length());
30 | }
31 |
32 | @Test
33 | public void testGenerateAdminPassword() {
34 | String password = "adminadmin";
35 | Assert.assertNotNull(PasswordUtils.getMd5Hash(password));
36 | }
37 |
38 | @Test
39 | public void testPadOrTrimTo16() {
40 | Assert.assertEquals("0000000000000000", PasswordUtils.padOrTrimTo16(null));
41 | Assert.assertEquals("0000000000000000", PasswordUtils.padOrTrimTo16(""));
42 | Assert.assertEquals("0000000000000001", PasswordUtils.padOrTrimTo16("1"));
43 | Assert.assertEquals("1234567812345678", PasswordUtils.padOrTrimTo16("1234567812345678"));
44 | Assert.assertEquals("1234567812345678", PasswordUtils.padOrTrimTo16("12345678123456789"));
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/test/resources/application-test.properties:
--------------------------------------------------------------------------------
1 | spring.datasource.url=jdbc:sqlite::memory:
2 | spring.datasource.driverClassName=org.sqlite.JDBC
--------------------------------------------------------------------------------
/src/test/resources/logback-test.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/third-party-license/license-axios:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014-present Matt Zabriskie
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
--------------------------------------------------------------------------------
/third-party-license/license-echarts-for-react:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) hustcc
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/third-party-license/license-fontawesome:
--------------------------------------------------------------------------------
1 | Font Awesome Free License
2 | -------------------------
3 |
4 | Font Awesome Free is free, open source, and GPL friendly. You can use it for
5 | commercial projects, open source projects, or really almost whatever you want.
6 | Full Font Awesome Free license: https://fontawesome.com/license/free.
7 |
8 | # Icons: CC BY 4.0 License (https://creativecommons.org/licenses/by/4.0/)
9 | In the Font Awesome Free download, the CC BY 4.0 license applies to all icons
10 | packaged as SVG and JS file types.
11 |
12 | # Fonts: SIL OFL 1.1 License (https://scripts.sil.org/OFL)
13 | In the Font Awesome Free download, the SIL OFL license applies to all icons
14 | packaged as web and desktop font files.
15 |
16 | # Code: MIT License (https://opensource.org/licenses/MIT)
17 | In the Font Awesome Free download, the MIT license applies to all non-font and
18 | non-icon files.
19 |
20 | # Attribution
21 | Attribution is required by MIT, SIL OFL, and CC BY licenses. Downloaded Font
22 | Awesome Free files already contain embedded comments with sufficient
23 | attribution, so you shouldn't need to do anything additional when using these
24 | files normally.
25 |
26 | We've kept attribution comments terse, so we ask that you do not actively work
27 | to remove them from files, especially code. They're a great way for folks to
28 | learn about Font Awesome.
29 |
30 | # Brand Icons
31 | All brand icons are trademarks of their respective owners. The use of these
32 | trademarks does not indicate endorsement of the trademark holder by Font
33 | Awesome, nor vice versa. **Please do not use brand logos for any purpose except
34 | to represent the company, product, or service to which they refer.**
--------------------------------------------------------------------------------
/third-party-license/license-react:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Facebook, Inc. and its affiliates.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/third-party-license/license-react-ace:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 James Hrisho
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/third-party-license/license-react-beautiful-dnd:
--------------------------------------------------------------------------------
1 | Copyright 2019 Atlassian Pty Ltd
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
--------------------------------------------------------------------------------
/third-party-license/license-react-color:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Case Sandberg
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/third-party-license/license-react-i18next:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 i18next
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/third-party-license/license-react-router:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) React Training 2016-2018
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/third-party-license/license-react-select:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2018 Jed Watson
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/third-party-license/license-react-table:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Tanner Linsley
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/third-party-license/license-react-toastify:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Fadi Khadra
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/upgrade/poli_upgrade_v0.10.0.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE p_report ADD project TEXT;
2 |
3 | CREATE TABLE
4 | IF NOT EXISTS p_user_favourite (
5 | user_id INTEGER NOT NULL,
6 | report_id INTEGER NOT NULL,
7 | PRIMARY KEY (user_id, report_id),
8 | FOREIGN KEY (user_id) REFERENCES p_user(id),
9 | FOREIGN KEY (report_id) REFERENCES p_report(id)
10 | );
11 |
12 | CREATE TABLE
13 | IF NOT EXISTS p_shared_report (
14 | id INTEGER NOT NULL PRIMARY KEY,
15 | share_key VARCHAR NOT NULL,
16 | report_id INTEGER NOT NULL,
17 | report_type VARCHAR NOT NULL,
18 | user_id INTEGER NOT NULL,
19 | created_at INTEGER NOT NULL,
20 | expired_by INTEGER NOT NULL,
21 | FOREIGN KEY (report_id) REFERENCES p_report(id),
22 | FOREIGN KEY (user_id) REFERENCES p_user(id)
23 | );
--------------------------------------------------------------------------------
/upgrade/poli_upgrade_v0.12.0.sql:
--------------------------------------------------------------------------------
1 | -- SQLite
2 | CREATE TABLE
3 | IF NOT EXISTS p_saved_query (
4 | id INTEGER NOT NULL PRIMARY KEY,
5 | datasource_id INTEGER,
6 | name TEXT NOT NULL UNIQUE,
7 | sql_query TEXT,
8 | endpoint_name TEXT UNIQUE,
9 | endpoint_accesscode TEXT
10 | );
11 |
12 | CREATE TABLE
13 | IF NOT EXISTS p_audit_log (
14 | id INTEGER NOT NULL PRIMARY KEY,
15 | created_at INTEGER NOT NULL,
16 | type TEXT NOT NULL,
17 | data TEXT
18 | );
19 |
20 | CREATE INDEX idx_audit_log_created_at ON p_audit_log (created_at);
21 |
--------------------------------------------------------------------------------
/upgrade/poli_upgrade_v0.7.0.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE
2 | IF NOT EXISTS p_canned_report (
3 | id INTEGER NOT NULL PRIMARY KEY,
4 | user_id INTEGER NOT NULL,
5 | created_at INTEGER NOT NULL,
6 | name TEXT NOT NULL,
7 | data TEXT,
8 | FOREIGN KEY (user_id) REFERENCES p_user(id)
9 | );
--------------------------------------------------------------------------------
/upgrade/poli_upgrade_v0.9.0.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE
2 | IF NOT EXISTS p_user_attribute (
3 | user_id INTEGER NOT NULL,
4 | attr_key TEXT NOT NULL,
5 | attr_value TEXT,
6 | FOREIGN KEY (user_id) REFERENCES p_user(id)
7 | );
--------------------------------------------------------------------------------
/version:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | VERSION=0.12.2
3 |
4 | echo $VERSION
5 |
--------------------------------------------------------------------------------
/web-app/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/web-app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "poli-web-app",
3 | "version": "0.12.2",
4 | "private": true,
5 | "dependencies": {
6 | "@fortawesome/fontawesome-svg-core": "^1.2.28",
7 | "@fortawesome/free-regular-svg-icons": "^5.13.0",
8 | "@fortawesome/free-solid-svg-icons": "^5.13.0",
9 | "@fortawesome/react-fontawesome": "^0.1.9",
10 | "axios": "^0.18.1",
11 | "echarts": "^4.7.0",
12 | "echarts-for-react": "^2.0.15-beta.1",
13 | "i18next": "^17.3.1",
14 | "react": "^16.13.1",
15 | "react-ace": "^6.6.0",
16 | "react-beautiful-dnd": "^13.0.0",
17 | "react-color": "^2.18.0",
18 | "react-dom": "^16.13.1",
19 | "react-i18next": "^10.13.2",
20 | "react-router-dom": "^5.1.2",
21 | "react-scripts": "3.4.1",
22 | "react-select": "^3.1.0",
23 | "react-table": "^6.11.5",
24 | "react-toastify": "^5.5.0"
25 | },
26 | "scripts": {
27 | "start": "react-scripts start",
28 | "build": "react-scripts build",
29 | "test": "react-scripts test",
30 | "eject": "react-scripts eject"
31 | },
32 | "eslintConfig": {
33 | "extends": "react-app"
34 | },
35 | "browserslist": []
36 | }
37 |
--------------------------------------------------------------------------------
/web-app/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shzlw/poli/5b90d6adc6f839866cb77854ad33f3cd9e1e26e8/web-app/public/favicon.ico
--------------------------------------------------------------------------------
/web-app/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Poli
8 |
9 |
10 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/web-app/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "Poli",
3 | "name": "Poli",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": ".",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/web-app/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div');
7 | ReactDOM.render(, div);
8 | ReactDOM.unmountComponentAtNode(div);
9 | });
10 |
--------------------------------------------------------------------------------
/web-app/src/api/ApiService.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 |
3 | export const fetchJdbcdatasources = async () => {
4 | return await httpGet('/ws/jdbcdatasources');
5 | }
6 |
7 | export const fetchDatabaseSchema = async (jdbcDataSourceId) => {
8 | return await httpGet(`/ws/jdbcdatasources/schema/${jdbcDataSourceId}`);
9 | }
10 |
11 | export const runQuery = async (jdbcDataSourceId, sqlQuery, resultLimit = 100) => {
12 | const requestBody = {
13 | jdbcDataSourceId: jdbcDataSourceId,
14 | sqlQuery: sqlQuery,
15 | resultLimit: resultLimit
16 | };
17 | return await httpPost('/ws/jdbcquery/query', requestBody);
18 | }
19 |
20 | export const fetchAuditLogs = async (page, pageSize, searchValue) => {
21 | const parameters = {
22 | search: searchValue,
23 | page,
24 | size: pageSize
25 | };
26 |
27 | return await httpGet('/ws/audit-logs', parameters);
28 | }
29 |
30 | export const fetchSavedQueries = async (page, pageSize, searchValue) => {
31 | const parameters = {
32 | search: searchValue,
33 | page,
34 | size: pageSize
35 | };
36 |
37 | return await httpGet('/ws/saved-queries', parameters);
38 | }
39 |
40 |
41 | export const httpGet = async (url, parameters = {}) => {
42 | try {
43 | const response = await axios.get(url, {params: parameters});
44 | return response;
45 | } catch(error) {
46 | console.log(error);
47 | return {};
48 | }
49 | }
50 |
51 | export const httpPost = async (url, requestBody = {}) => {
52 | try {
53 | const response = await axios.post(url, requestBody);
54 | return response;
55 | } catch(error) {
56 | console.log(error);
57 | return {};
58 | }
59 | }
60 |
61 | export const httpPut = async (url, requestBody = {}) => {
62 | try {
63 | const response = await axios.put(url, requestBody);
64 | return response;
65 | } catch(error) {
66 | console.log(error);
67 | return {};
68 | }
69 | }
70 |
71 | export const httpDelete = async (url, parameters = {}) => {
72 | try {
73 | const response = await axios.delete(url, {params: parameters});
74 | return response;
75 | } catch(error) {
76 | console.log(error);
77 | return {};
78 | }
79 | }
80 |
81 |
--------------------------------------------------------------------------------
/web-app/src/api/Constants.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Constants
3 | */
4 | export const STATIC = 'static';
5 | export const CHART = 'chart';
6 | export const FILTER = 'filter';
7 | export const COMPONENT_TYPES = [STATIC, CHART, FILTER];
8 |
9 | export const SLICER = 'slicer';
10 | export const SINGLE_VALUE= 'single';
11 | export const DATE_PICKER = 'date picker';
12 | export const FILTER_TYPES = [SLICER, SINGLE_VALUE, DATE_PICKER];
13 |
14 | export const PIE = 'pie';
15 | export const TABLE = 'table';
16 | export const LINE = 'line';
17 | export const AREA = 'area';
18 | export const BAR = 'bar';
19 | export const HEATMAP = 'heatmap';
20 | export const TREEMAP = 'treemap';
21 | export const FUNNEL = 'funnel';
22 | export const CARD = 'card';
23 | export const KANBAN = 'kanban';
24 | export const CHART_TYPES = [TABLE, PIE, LINE, BAR, AREA, CARD, FUNNEL, TREEMAP, HEATMAP, KANBAN];
25 |
26 | export const IMAGE = 'image';
27 | export const TEXT = 'text';
28 | export const HTML = 'html';
29 | export const IFRAME = 'iframe';
30 | export const STATIC_TYPES = [TEXT, IMAGE, IFRAME, HTML];
31 |
32 |
33 | export const DEFAULT_FILTER_VIEW_WIDTH = 200;
34 |
35 | export const SYS_ROLE_ADMIN = 'admin';
36 | export const SYS_ROLE_DEVELOPER = 'developer';
37 | export const SYS_ROLE_VIEWER = 'viewer';
38 |
39 | export const DEFAULT_REPORT_HEIGHT = 600;
40 | export const DEFAULT_REPORT_FIXED_WIDTH = 800;
41 |
42 | export const REMEMBERME = 'rememberMe';
43 | export const YES = 'yes';
44 | export const NO = 'no';
45 |
46 | export const COLOR_SLATE = 'rgba(9, 30, 66, 1)';
47 | export const COLOR_WHITE = 'rgba(255, 255, 255, 1)';
48 |
49 | export const DEFAULT = 'default';
50 | export const VINTAGE = 'vintage';
51 | export const ROMA = 'roma';
52 | export const MACARONS = 'macarons';
53 | export const SHINE = 'shine';
54 | export const CHART_COLOR_PLATETTES = [DEFAULT, VINTAGE, ROMA, MACARONS, SHINE];
55 |
56 | export const ADHOC = 'adhoc';
57 | export const CANNED = 'canned';
58 |
59 | export const DEFAULT_MIN_COLOR = 'rgba(222, 53, 11, 1)';
60 | export const DEFAULT_MAX_COLOR = 'rgba(0, 135, 90, 1)';
61 |
62 | export const REPORT = 'report';
63 |
64 |
--------------------------------------------------------------------------------
/web-app/src/api/Util.js:
--------------------------------------------------------------------------------
1 |
2 | export const isArrayEmpty = (array) => {
3 | return !Array.isArray(array) || !array.length;
4 | };
5 |
6 | export const jsonToArray = (json) => {
7 | let array;
8 | try {
9 | array = JSON.parse(json);
10 | } catch(e) {
11 | array = [];
12 | }
13 | return array;
14 | }
15 |
16 | export const getReadableDiffTime = (d1, d2) => {
17 | const seconds = Math.abs(d1 - d2) / 1000;
18 | if (seconds <= 5) {
19 | return 'Just now';
20 | } else if (seconds > 5 && seconds < 60) {
21 | return Math.floor(seconds) + ' seconds ago';
22 | }
23 |
24 | const minutes = Math.floor(seconds / 60);
25 | if (minutes === 1) {
26 | return '1 minute ago';
27 | } else if (minutes > 1 && minutes < 60) {
28 | return minutes + " minutes ago";
29 | }
30 |
31 | const hours = Math.floor(seconds / 3600);
32 | if (hours === 1) {
33 | return '1 hour ago';
34 | }
35 | return hours + " hours ago";
36 | }
37 |
38 | export const leftPadZero = (n) => {
39 | return parseInt(n, 10) < 10 ? '0' + n : n;
40 | }
41 |
42 | // Spring rest error
43 | // {"timestamp":"2019-12-18T20:50:29.009+0000",
44 | // "status":405,
45 | // "error":"Method Not Allowed",
46 | // "message":"Request method 'GET' not supported",
47 | // "path":"/auth/signup"}
48 | export const toReadableServerError = (error) => {
49 | const resData = error.response.data || {};
50 | const serverError = resData.error;
51 | const serverMsg = resData.message;
52 | const displayError = serverError + ": " + serverMsg;
53 | return displayError;
54 | }
55 |
--------------------------------------------------------------------------------
/web-app/src/components/Checkbox/Checkbox.css:
--------------------------------------------------------------------------------
1 | .checkbox-container {
2 | display: flex;
3 | flex-flow: row;
4 | height: 20px;
5 | }
6 |
7 | .checkbox-checkmark {
8 | width: 20px;
9 | height: 20px;
10 | flex: 0 1 auto;
11 | cursor: pointer;
12 | }
13 |
14 | .checkbox-label {
15 | flex: 1 1 auto;
16 | white-space: nowrap;
17 | overflow: hidden;
18 | text-overflow: ellipsis;
19 | }
--------------------------------------------------------------------------------
/web-app/src/components/Checkbox/Checkbox.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import './Checkbox.css';
4 |
5 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
6 |
7 |
8 | class Checkbox extends React.Component {
9 | constructor(props) {
10 | super(props);
11 | this.state = {
12 | };
13 | }
14 |
15 | static propTypes = {
16 | name: PropTypes.string.isRequired,
17 | value: PropTypes.string.isRequired,
18 | checked: PropTypes.bool.isRequired,
19 | onChange: PropTypes.func.isRequired,
20 | readOnly: PropTypes.bool
21 | };
22 |
23 | toggle = () => {
24 | const {
25 | name,
26 | checked,
27 | readOnly = false
28 | } = this.props;
29 |
30 | if (readOnly) {
31 | return;
32 | }
33 |
34 | this.props.onChange(name, !checked);
35 | }
36 |
37 | render() {
38 | const {
39 | value,
40 | checked
41 | } = this.props;
42 |
43 | return (
44 |
45 |
46 | { checked ? (
47 |
48 | ) : (
49 |
50 | )}
51 |
52 |
{value}
53 |
54 | );
55 | }
56 | }
57 |
58 | export default Checkbox;
59 |
--------------------------------------------------------------------------------
/web-app/src/components/ColorPicker/ColorPicker.css:
--------------------------------------------------------------------------------
1 | .colorpicker-select {
2 | background-color: #373737;
3 | border: 1px solid #00001c;
4 | display: inline-block;
5 | box-shadow: rgba(50, 50, 93, 0.149) 0px 1px 3px, rgba(0, 0, 0, 0.0196) 0px 1px 0px;
6 | border-radius: 4px;
7 | border: 0;
8 | outline: none;
9 | cursor: pointer;
10 | }
11 |
12 | .colorpicker-color {
13 | width: 36px;
14 | height: 20px;
15 | border-radius: 4px;
16 | }
17 |
18 | .colorpicker-popover {
19 | position: fixed;
20 | z-index: 90;
21 | }
22 |
23 | .colorpicker-overlay {
24 | position: fixed;
25 | top: 0;
26 | left: 0;
27 | right: 0;
28 | bottom: 0;
29 | z-index: 89;
30 | }
--------------------------------------------------------------------------------
/web-app/src/components/ColorPicker/ColorPicker.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ChromePicker } from 'react-color';
3 | import './ColorPicker.css'
4 |
5 | class ColorPicker extends React.Component {
6 |
7 | constructor(props) {
8 | super(props);
9 | this.state = {
10 | showPalette: false
11 | }
12 | }
13 |
14 | handleClick = () => {
15 | this.setState(prevState => ({
16 | showPalette: !prevState.showPalette
17 | }));
18 | };
19 |
20 | handleChange = (color) => {
21 | const { name } = this.props;
22 | const rgb = color.rgb;
23 | const rgba = `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${rgb.a})`;
24 | this.props.onChange(name, rgba);
25 | };
26 |
27 | close = () => {
28 | this.setState({
29 | showPalette: false
30 | });
31 | }
32 |
33 | render() {
34 |
35 | const color = {
36 | background: this.props.value
37 | };
38 |
39 | return (
40 |
41 |
44 | { this.state.showPalette && (
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | )}
53 |
54 | )
55 | };
56 | }
57 |
58 | export default ColorPicker;
--------------------------------------------------------------------------------
/web-app/src/components/DropdownDialog/DropdownDialog.css:
--------------------------------------------------------------------------------
1 | .dropdown-overlay {
2 | position: fixed;
3 | top: 0;
4 | left: 0;
5 | right: 0;
6 | bottom: 0;
7 | z-index: 150;
8 | }
9 |
10 | .dropdown-panel {
11 | position: fixed;
12 | z-index: 151;
13 | box-shadow: rgba(50, 50, 93, 0.149) 0px 1px 3px, rgba(0, 0, 0, 0.0196) 0px 1px 0px;
14 | border-radius: 4px;
15 | top: 85px;
16 | right: 35px;
17 | background-color: #FFFFFF;
18 | border: 1px solid #e0e0e0;
19 | }
20 |
21 | .dropdown-body {
22 | padding: 8px;
23 | }
--------------------------------------------------------------------------------
/web-app/src/components/DropdownDialog/DropdownDialog.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './DropdownDialog.css';
3 |
4 | class DropdownDialog extends React.Component {
5 |
6 | render() {
7 | const {
8 | show = false,
9 | children
10 | } = this.props;
11 |
12 | const displayClass = show ? 'display-block' : 'display-none';
13 | return (
14 | this.props.onClose()}>
15 |
16 |
17 | {children}
18 |
19 |
20 |
21 | );
22 | }
23 | }
24 |
25 | export default DropdownDialog;
--------------------------------------------------------------------------------
/web-app/src/components/GridLayout.css:
--------------------------------------------------------------------------------
1 |
2 | .grid-layout {
3 | position: relative;
4 | background-color: #FFFFFF;
5 | }
6 |
7 | .grid-box {
8 | position: absolute;
9 | overflow-x: hidden;
10 | overflow-y: hidden;
11 | display: flex;
12 | flex-flow: column;
13 | height: 100%;
14 | }
15 |
16 | .grid-box-title {
17 | padding: 3px 6px;
18 | flex: 0 1 auto;
19 | }
20 |
21 | .grid-box-title-value {
22 | font-size: 18px;
23 | }
24 |
25 | .grid-box-content {
26 | flex: 1 1 auto;
27 | overflow-y: auto;
28 | }
29 |
30 | .grid-box-content-panel {
31 | padding: 0px 5px;
32 | z-index: 1;
33 | }
34 |
35 | .grid-title-button-panel {
36 | position: absolute;
37 | right: 0;
38 | top: 0;
39 | background-color: transparent;
40 | padding: 4px;
41 | z-index: 2;
42 | min-width: 20px;
43 | height: 20px;
44 | }
45 |
46 | .grid-draggable {
47 | cursor: move;
48 | height: 25px;
49 | position: absolute;
50 | left: 0px;
51 | top: 0px;
52 | right: 0px;
53 | background-color: transparent;
54 | z-index: 10;
55 | }
56 |
57 | .grid-resizable {
58 | width: 12px;
59 | height: 12px;
60 | position: absolute;
61 | right: 0;
62 | bottom: 0;
63 | cursor: se-resize;
64 | z-index: 10;
65 | }
66 |
67 | .download-csv-button {
68 | display: none;
69 | z-index: 10;
70 | }
71 |
72 | .grid-title-button-panel:hover .download-csv-button {
73 | display: inline-block;
74 | transition: 0.3s;
75 | }
--------------------------------------------------------------------------------
/web-app/src/components/GridLayout.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import GridItem from './GridItem';
4 | import './GridLayout.css';
5 |
6 | class GridLayout extends React.Component {
7 |
8 | constructor(props) {
9 | super(props);
10 | this.state = {
11 | };
12 | }
13 |
14 | render() {
15 | const {
16 | width,
17 | height,
18 | backgroundColor,
19 | components = [],
20 | showGridlines,
21 | isEditMode
22 | } = this.props;
23 |
24 | let style = {
25 | width: width + 'px',
26 | height: height + 'px',
27 | backgroundColor: backgroundColor
28 | };
29 |
30 | if (showGridlines && isEditMode) {
31 | style.backgroundSize = '15px 15px';
32 | style.backgroundImage = 'radial-gradient(rgb(23, 43, 77) 10%, transparent 10%)';
33 | }
34 |
35 | const boxItems = components.map((component, index) =>
36 |
50 | );
51 |
52 |
53 | return (
54 |
57 | {boxItems}
58 |
59 | )
60 | }
61 | }
62 |
63 | export default GridLayout;
--------------------------------------------------------------------------------
/web-app/src/components/Kanban/Kanban.css:
--------------------------------------------------------------------------------
1 | .kanban-container {
2 | width: inherit;
3 | height: inherit;
4 |
5 | display: flex;
6 | flex-direction: row;
7 | }
8 |
9 | .kanban-column {
10 | flex: 1;
11 | display: flex;
12 | flex-direction: column;
13 | }
14 |
15 | .kanban-group-title {
16 | flex: 0 1 auto;
17 | text-align: center;
18 | font-weight: bold;
19 | font-size: 18px;
20 | }
21 |
22 | .kanban-group-panel {
23 | flex: 1;
24 | overflow-y: auto;
25 | }
26 |
27 | .kanban-block {
28 | box-shadow: rgba(50, 50, 93, 0.149) 0px 1px 3px, rgba(0, 0, 0, 0.0196) 0px 1px 0px;
29 | border: 1px solid #e0e0e0;
30 | outline: 0;
31 | border-radius: 4px;
32 | padding: 5px;
33 | margin: 8px 5px;
34 | background-color: #FFFFFF;
35 | text-align: center;
36 | }
37 |
38 | .kanban-block-title {
39 | font-weight: bold;
40 | font-size: 16px;
41 | border-bottom: 1px solid black;
42 | padding-bottom: 5px;
43 | }
44 |
45 | .kanban-block-body {
46 | padding-top: 5px;
47 | }
48 |
49 | .kanban-block-body-row {
50 | padding-bottom: 5px;
51 | }
--------------------------------------------------------------------------------
/web-app/src/components/Kanban/Kanban.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import './Kanban.css';
4 |
5 | class Kanban extends React.Component {
6 |
7 | constructor(props) {
8 | super(props);
9 | this.state = {
10 | }
11 | }
12 |
13 | static propTypes = {
14 | groupByField: PropTypes.string.isRequired,
15 | data: PropTypes.array.isRequired,
16 | blockTitleField: PropTypes.string
17 | };
18 |
19 | componentDidMount() {
20 | }
21 |
22 | render() {
23 | // TODO: allow to setup fixed width for column?
24 | // If the width is defined, the panel should be scrollable.
25 | // TODO: sort the blocks based on orderByField?
26 | const {
27 | groupByField,
28 | blockTitleField,
29 | data = []
30 | } = this.props;
31 |
32 | const groupData = [];
33 | for (let i = 0; i < data.length; i++) {
34 | const row = data[i];
35 | const index = groupData.findIndex(g => g.groupBy === row[groupByField]);
36 | if (index === -1) {
37 | groupData.push({
38 | groupBy: row[groupByField],
39 | blocks: [row]
40 | });
41 | } else {
42 | groupData[index].blocks.push(row);
43 | }
44 | }
45 |
46 | const groupPanelItems = [];
47 | for (let i = 0; i < groupData.length; i++) {
48 | const {
49 | groupBy,
50 | blocks
51 | } = groupData[i];
52 |
53 | const blockItems = [];
54 | for (let j = 0; j < blocks.length; j++) {
55 | const blockRowItems = [];
56 | let blockTitleValue = null;
57 | for (let [key, value] of Object.entries(blocks[j])) {
58 | if (key === blockTitleField) {
59 | blockTitleValue = value;
60 | } else if (key !== groupByField) {
61 | blockRowItems.push(
62 | {value}
63 | );
64 | }
65 | }
66 |
67 | blockItems.push(
68 |
69 | { blockTitleValue && (
70 |
{blockTitleValue}
71 | )}
72 |
73 | {blockRowItems}
74 |
75 |
76 | );
77 | }
78 |
79 | groupPanelItems.push(
80 |
81 |
{groupBy}
82 |
83 | {blockItems}
84 |
85 |
86 | );
87 | }
88 |
89 | return (
90 |
91 | {groupPanelItems}
92 |
93 | );
94 | }
95 | }
96 |
97 | export default Kanban;
--------------------------------------------------------------------------------
/web-app/src/components/Modal/Modal.css:
--------------------------------------------------------------------------------
1 | .modal-overlay {
2 | background: rgba(0, 0, 0, 0.6);
3 | position: fixed;
4 | top: 0;
5 | left: 0;
6 | right: 0;
7 | bottom: 0;
8 | z-index: 150;
9 | }
10 |
11 | .model-title {
12 | font-size: 22px;
13 | float: left;
14 | }
15 |
16 | .model-close-button {
17 | color: #212121;
18 | float: right;
19 | cursor: pointer;
20 | }
21 |
22 | .model-close-button:hover {
23 | background-color: #212121;
24 | color: #FFFFFF;
25 | }
26 |
27 | .modal-panel {
28 | background-color: #efefef;
29 | position: fixed;
30 | overflow-y: auto;
31 | padding: 10px;
32 | z-index: 200;
33 | box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23);
34 | max-height: calc(100vh - 60px);
35 | }
--------------------------------------------------------------------------------
/web-app/src/components/Modal/Modal.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './Modal.css';
3 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
4 |
5 | class Modal extends React.Component {
6 |
7 | render() {
8 | const {
9 | show,
10 | modalClass = '',
11 | title,
12 | children
13 | } = this.props;
14 |
15 | const modalStatus = show ? 'display-block' : 'display-none';
16 | return (
17 |
18 |
19 |
20 |
{title}
21 |
24 |
25 |
26 |
27 | {children}
28 |
29 |
30 |
31 | );
32 | }
33 | }
34 |
35 | export default Modal;
--------------------------------------------------------------------------------
/web-app/src/components/SearchInput/SearchInput.css:
--------------------------------------------------------------------------------
1 | .search-input-container {
2 | box-shadow: rgba(50, 50, 93, 0.149) 0px 1px 3px, rgba(0, 0, 0, 0.0196) 0px 1px 0px;
3 | display: flex;
4 | flex-direction: row;
5 | width: inherit;
6 | box-sizing: border-box;
7 | border: 1px solid #e0e0e0;
8 | border-radius: 4px;
9 | }
10 |
11 | .search-input-icon {
12 | width: 32px;
13 | height: 31px;
14 | background-color: #FFFFFF;
15 | border-radius: 4px 0px 0px 4px;
16 | }
17 |
18 | .search-input-input {
19 | -webkit-appearance: none;
20 | -webkit-box-sizing: border-box;
21 | -moz-box-sizing: border-box;
22 | box-sizing: border-box;
23 | width: calc(100% - 64px);
24 | margin: 0px !important;
25 | padding: 5px 0px !important;
26 | box-shadow: none !important;
27 | border-radius: 0 !important;
28 | border: 2px solid #FFFFFF;
29 | outline: 0;
30 | background: #FFFFFF;
31 | font-size: 16px;
32 | height: 31px;
33 | }
34 |
35 | .search-input-input:focus {
36 | border: 2px solid #FFFFFF !important;
37 | }
38 |
39 | .search-input-reset-button {
40 | border-radius: 0px 4px 4px 0px;
41 | width: 32px;
42 | height: 31px;
43 | box-shadow: none;
44 | background-color: #FFFFFF;
45 | color: #212121;
46 | border: 0;
47 | }
48 |
49 | .search-input-reset-button:hover {
50 | background-color: #FFFFFF;
51 | color: #212121;
52 | }
53 |
54 |
--------------------------------------------------------------------------------
/web-app/src/components/SearchInput/SearchInput.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
4 | import './SearchInput.css';
5 |
6 | class SearchInput extends React.Component {
7 |
8 | constructor(props) {
9 | super(props);
10 | this.state = {};
11 | }
12 |
13 | static propTypes = {
14 | name: PropTypes.string.isRequired,
15 | value: PropTypes.string.isRequired,
16 | onChange: PropTypes.func.isRequired,
17 | inputWidth: PropTypes.number
18 | };
19 |
20 | handleInputChange = (event) => {
21 | const { name } = this.props;
22 | const value = event.target.value;
23 | this.props.onChange(name, value);
24 | }
25 |
26 | reset = () => {
27 | const { name } = this.props;
28 | this.props.onChange(name, '');
29 | }
30 |
31 | render() {
32 | const {
33 | name,
34 | value,
35 | inputWidth
36 | } = this.props;
37 |
38 | let inputStyle = {};
39 | if (inputWidth) {
40 | inputStyle.width = inputWidth + 'px';
41 | }
42 |
43 | return (
44 |
45 |
46 |
47 |
48 |
56 |
61 |
62 | );
63 | }
64 | }
65 |
66 | export default SearchInput;
--------------------------------------------------------------------------------
/web-app/src/components/Select.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | class Select extends React.Component {
5 |
6 | constructor(props) {
7 | super(props);
8 | this.state = {};
9 | }
10 |
11 | static propTypes = {
12 | name: PropTypes.string.isRequired,
13 | value: PropTypes.string.isRequired,
14 | onChange: PropTypes.func.isRequired,
15 | options: PropTypes.array.isRequired,
16 | optionDisplay: PropTypes.string,
17 | optionValue: PropTypes.string,
18 | preloadOneEmpty: PropTypes.bool
19 | };
20 |
21 | handleOptionChange = (event) => {
22 | const name = this.props.name;
23 | const value = event.target.value;
24 | this.props.onChange(name, value);
25 | }
26 |
27 | render() {
28 | const {
29 | value,
30 | options = [],
31 | optionValue,
32 | optionDisplay,
33 | preloadOneEmpty = true
34 | } = this.props;
35 |
36 | const optionList = [];
37 | if (preloadOneEmpty) {
38 | optionList.push(
39 |
40 | );
41 | }
42 |
43 | options.forEach((option, index) => {
44 | let value;
45 | let display;
46 | if (optionValue && optionDisplay) {
47 | // The options contain objects.
48 | value = option[optionValue];
49 | display = option[optionDisplay];
50 | } else {
51 | // The options contain string or number.
52 | value = option;
53 | display = option;
54 | }
55 |
56 | optionList.push(
57 |
58 | )
59 | });
60 |
61 | return (
62 |
68 | )
69 | };
70 | }
71 |
72 | export default Select;
--------------------------------------------------------------------------------
/web-app/src/components/SelectButtons/SelectButtons.css:
--------------------------------------------------------------------------------
1 | .selectbuttons-container {
2 | display: flex;
3 | flex-direction: row;
4 | width: inherit;
5 | box-sizing: border-box;
6 | }
7 |
8 | .selectbuttons-container .select-button {
9 | margin-right: 5px;
10 | min-width: 100px;
11 | text-align: center;
12 | border-radius: 4px;
13 | font-weight: 700;
14 | height: 32px;
15 | line-height: 32px;
16 | cursor: pointer;
17 | background-color: #FFFFFF;
18 | border: 0;
19 | outline: none;
20 | color: #8993A4;
21 | box-shadow: rgba(50, 50, 93, 0.149) 0px 1px 3px, rgba(0, 0, 0, 0.0196) 0px 1px 0px;
22 | flex-grow: 1;
23 | }
24 |
25 | .selectbuttons-container .select-button:hover {
26 | background-color: #C1C7D0;
27 | }
28 |
29 | .selectbuttons-container .select-button-active {
30 | background-color: #212121;
31 | color: white;
32 | }
33 |
34 |
35 |
--------------------------------------------------------------------------------
/web-app/src/components/SelectButtons/SelectButtons.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import './SelectButtons.css';
4 |
5 | class SelectButtons extends React.Component {
6 |
7 | constructor(props) {
8 | super(props);
9 | this.state = {};
10 | }
11 |
12 | static propTypes = {
13 | name: PropTypes.string.isRequired,
14 | value: PropTypes.string.isRequired,
15 | selections: PropTypes.array.isRequired,
16 | onChange: PropTypes.func.isRequired
17 | };
18 |
19 | clickButton = (value) => {
20 | const { name } = this.props;
21 | this.props.onChange(name, value);
22 | }
23 |
24 | render() {
25 | const {
26 | value,
27 | selections = []
28 | } = this.props;
29 | const buttonItems = [];
30 | for (let i = 0; i < selections.length; i++) {
31 | const {
32 | display,
33 | value: actualValue
34 | } = selections[i];
35 | const activeClass = actualValue === value ? 'select-button-active' : '';
36 | buttonItems.push(
37 | this.clickButton(actualValue)} key={actualValue}>{display}
38 | )
39 | }
40 |
41 | return (
42 |
43 | {buttonItems}
44 |
45 | )
46 | };
47 | }
48 |
49 | export default SelectButtons;
--------------------------------------------------------------------------------
/web-app/src/components/Tabs/Tabs.css:
--------------------------------------------------------------------------------
1 | .tab-header {
2 | list-style-type: none;
3 | border-bottom: 1px solid #ccc;
4 | padding: 0px;
5 | margin: 0px;
6 | }
7 |
8 | .tab-header-item {
9 | display: inline-block;
10 | list-style: none;
11 | margin-bottom: -1px;
12 | padding: 6px 12px;
13 | cursor: pointer;
14 | color: #8993A4;
15 | }
16 |
17 | .tab-header-active {
18 | border-bottom: 3px solid #0065FF;
19 | color: #212121;
20 | }
21 |
22 | .tab-content {
23 | }
--------------------------------------------------------------------------------
/web-app/src/components/Tabs/Tabs.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './Tabs.css';
3 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
4 |
5 | class Tabs extends React.Component {
6 |
7 | handleTabClick = (title) => {
8 | this.props.onTabChange(title);
9 | }
10 |
11 | render() {
12 | const {
13 | activeTab,
14 | children,
15 | showTabBgColor = false
16 | } = this.props;
17 |
18 | const tabHeaders = [];
19 | let tabContent = null;
20 | for (let i = 0; i < children.length; i++) {
21 | if (children[i]) {
22 | const {
23 | title,
24 | icon,
25 | iconOnly = false
26 | } = children[i].props;
27 |
28 | let active = '';
29 | let tabStyle = {};
30 | if (title === activeTab) {
31 | active = 'tab-header-active';
32 | tabContent = children[i].props.children;
33 | tabStyle = showTabBgColor ? {backgroundColor: '#FFFFFF'} : {};
34 | }
35 |
36 | tabHeaders.push(
37 | this.handleTabClick(title)}>
38 | { iconOnly ? (
39 |
40 | ) : (
41 | {title}
42 | )}
43 |
44 | );
45 | }
46 | }
47 |
48 | return (
49 |
50 |
53 |
54 | {tabContent}
55 |
56 |
57 | );
58 | }
59 | }
60 |
61 | export default Tabs;
62 |
63 |
--------------------------------------------------------------------------------
/web-app/src/components/Toast/Toast.css:
--------------------------------------------------------------------------------
1 | .toast-container {
2 | text-align: center;
3 | font-weight: 700;
4 | color: #FFFFFF;
5 | position: fixed;
6 | padding: 10px;
7 | z-index: 300;
8 | left: 50%;
9 | width: 400px;
10 | top: 10px;
11 | margin-left: -200px;
12 | box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23);
13 | overflow-y: auto;
14 | }
--------------------------------------------------------------------------------
/web-app/src/components/Toast/Toast.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './Toast.css';
3 |
4 | const COLOR_RED = '#ef5350';
5 | const COLOR_GREEN = '#66bb6a';
6 |
7 | class Toast extends React.Component {
8 |
9 | constructor(props) {
10 | super(props);
11 | this.state = {
12 | showToast: false,
13 | message: '',
14 | backgroundColor: COLOR_RED,
15 | timeoutId: ''
16 | };
17 |
18 | Toast._toastRef = this;
19 | }
20 |
21 | componentWillUnmount() {
22 | const { timeoutId } = this.state;
23 | if (timeoutId) {
24 | clearInterval(timeoutId);
25 | }
26 | }
27 |
28 | show = (message, bgColor) => {
29 | const { timeoutId } = this.state;
30 | if (timeoutId) {
31 | clearTimeout(timeoutId);
32 | }
33 | this.setState({
34 | showToast: true,
35 | message: message,
36 | backgroundColor: bgColor,
37 | timeoutId: ''
38 | }, () => {
39 | const timeoutId = setTimeout(() => {
40 | this.hide();
41 | }, 3000);
42 | this.setState({
43 | timeoutId: timeoutId
44 | });
45 | });
46 | }
47 |
48 | hide = () => {
49 | this.setState({
50 | showToast: false,
51 | message: ''
52 | });
53 | }
54 |
55 | static showSuccess = (message) => {
56 | Toast._toastRef.show(message, COLOR_GREEN);
57 | }
58 |
59 | static showError = (message) => {
60 | Toast._toastRef.show(message, COLOR_RED);
61 | }
62 |
63 | render() {
64 | const {
65 | showToast,
66 | message,
67 | backgroundColor
68 | } = this.state;
69 |
70 | const toastStatus = showToast ? 'display-block' : 'display-none';
71 | const style = {
72 | backgroundColor: backgroundColor
73 | }
74 |
75 | return (
76 |
77 | {message}
78 |
79 | );
80 | }
81 | }
82 |
83 | export default Toast;
--------------------------------------------------------------------------------
/web-app/src/components/filters/DatePicker.css:
--------------------------------------------------------------------------------
1 | .row:after {
2 | content: "";
3 | display: table;
4 | clear: both;
5 | }
6 |
7 | .calendar-container {
8 | padding-top: 5px;
9 | }
10 |
11 | .calendar-dialog {
12 | width: 189px;
13 | border: 1px solid #dfe1e6;
14 | position: fixed;
15 | background-color: #FFFFFF;
16 | box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23);
17 | }
18 |
19 | .calendar-cell {
20 | width: 25px;
21 | height: 25px;
22 | line-height: 25px;
23 | float: left;
24 | text-align: center;
25 | }
26 |
27 | .calendar-container .select-date {
28 | -webkit-box-sizing: border-box;
29 | -moz-box-sizing: border-box;
30 | box-sizing: border-box;
31 | box-shadow: none !important;
32 | border: 2px solid #dfe1e6 !important;
33 | padding: 5px 8px;
34 | cursor: pointer;
35 | width: 100%;
36 | background-color: #FFFFFF;
37 | }
38 |
39 | .calendar-container .header-cell {
40 | border: 1px solid #dfe1e6;
41 | background-color: #dfe1e6;
42 | color: #000000;
43 | font-weight: bold;
44 | }
45 |
46 | .calendar-container .body-cell {
47 | border: 1px solid #dfe1e6;
48 | }
49 |
50 | .calendar-container .clickable {
51 | cursor: pointer;
52 | }
53 |
54 | .calendar-container .clickable:hover {
55 | background-color: #0065FF;
56 | color: #FFFFFF;
57 | }
58 |
59 | .calendar-container .non-clickable {
60 | background-color: #dfe1e6;
61 | }
62 |
63 | .calendar-container .cell-active {
64 | background-color: #000000;
65 | color: #FFFFFF;
66 | }
67 |
68 | .calendar-container .display-date {
69 | float: left;
70 | width: 109px;
71 | height: 25px;
72 | text-align: center;
73 | }
74 |
75 | .calendar-container .calendar-button {
76 | color: #0065FF;
77 | width: 40px;
78 | cursor: pointer;
79 | }
80 |
81 | .calendar-container .select-year {
82 | height: 20px;
83 | margin-top: 3px;
84 | border: 1px solid #dfe1e6 !important;
85 | }
86 |
87 | .calendar-overlay {
88 | position: fixed;
89 | top: 0;
90 | left: 0;
91 | right: 0;
92 | bottom: 0;
93 | }
--------------------------------------------------------------------------------
/web-app/src/components/filters/InputRange.css:
--------------------------------------------------------------------------------
1 | .input-range-container {
2 | padding: 3px 10px 8px 5px;
3 | }
4 |
5 | .input-range {
6 | -webkit-appearance: none;
7 | width: 100%;
8 | height: 5px;
9 | border-radius: 8px;
10 | background: #cccccc;
11 | outline: none;
12 | opacity: 0.7;
13 | -webkit-transition: .2s;
14 | transition: opacity .2s;
15 | }
16 |
17 | .input-range:hover {
18 | opacity: 1;
19 | }
20 |
21 | .input-range::-webkit-slider-thumb {
22 | appearance: none;
23 | width: 20px;
24 | height: 20px;
25 | border-radius: 50%;
26 | background: #0065FF;
27 | cursor: pointer;
28 | }
29 |
30 | .input-range::-moz-range-thumb {
31 | width: 20px;
32 | height: 20px;
33 | border-radius: 50%;
34 | background: #0065FF;
35 | cursor: pointer;
36 | }
37 |
38 | .input-range-value {
39 | background-color: #373737;
40 | color: #dfdfdf;
41 | padding: 2px 4px;
42 | box-shadow: rgba(50, 50, 93, 0.149) 0px 1px 3px, rgba(0, 0, 0, 0.0196) 0px 1px 0px;
43 | outline: 0;
44 | border-radius: 4px;
45 | font-size: 16px;
46 | }
--------------------------------------------------------------------------------
/web-app/src/components/filters/InputRange.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import './InputRange.css';
4 |
5 | class InputRange extends React.Component {
6 |
7 | constructor(props) {
8 | super(props);
9 | this.state = {};
10 | }
11 |
12 | static propTypes = {
13 | name: PropTypes.string.isRequired,
14 | min: PropTypes.number.isRequired,
15 | max: PropTypes.number.isRequired,
16 | value: PropTypes.number.isRequired,
17 | onChange: PropTypes.func.isRequired,
18 | step: PropTypes.number.isRequired
19 | };
20 |
21 | handleInputChange = (event) => {
22 | const { name } = this.props;
23 | const value = Number(event.target.value);
24 | this.props.onChange(name, value);
25 | }
26 |
27 | render() {
28 | const {
29 | name,
30 | min,
31 | max,
32 | value,
33 | step
34 | } = this.props;
35 |
36 | return (
37 |
38 |
39 | {value}
40 |
41 |
51 |
52 | );
53 | }
54 | }
55 |
56 | export default InputRange;
--------------------------------------------------------------------------------
/web-app/src/components/filters/Paginator.css:
--------------------------------------------------------------------------------
1 |
2 | .paginator-button {
3 | border: 1px solid black;
4 | padding: 6px 12px;
5 | text-align: center;
6 | text-decoration: none;
7 | display: inline-block;
8 | font-size: 16px;
9 | cursor: pointer;
10 | }
--------------------------------------------------------------------------------
/web-app/src/components/filters/Paginator.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import './Paginator.css';
4 |
5 | class Paginator extends React.Component {
6 |
7 | constructor(props) {
8 | super(props);
9 | this.state = {};
10 | }
11 |
12 | static propTypes = {
13 | name: PropTypes.string.isRequired,
14 | value: PropTypes.number.isRequired,
15 | min: PropTypes.number,
16 | max: PropTypes.number
17 | };
18 |
19 | previous = () => {
20 | const {
21 | name,
22 | value,
23 | min = 1
24 | } = this.props;
25 | const current = Number(value);
26 | let prev = current - 1;
27 | if (prev < min) {
28 | prev = current;
29 | }
30 | this.props.onChange(name, prev);
31 | }
32 |
33 | next = () => {
34 | const {
35 | name,
36 | value,
37 | max = 100
38 | } = this.props;
39 | const current = Number(value);
40 | let next = current + 1;
41 | if (next > max) {
42 | next = current;
43 | }
44 | this.props.onChange(name, next);
45 | }
46 |
47 | clickNumber = (value) => {
48 | const {
49 | name
50 | } = this.props;
51 | const current = Number(value);
52 | this.props.onChange(name, current);
53 | }
54 |
55 | render() {
56 | const {
57 | value,
58 | min = 1,
59 | max = 100
60 | } = this.props;
61 |
62 | if (max <= min) {
63 | return (error: min >= max
);
64 | }
65 |
66 | if (value > max || value < min) {
67 | return (error: value out of range
);
68 | }
69 |
70 | let showPrevious = true;
71 | let showNext = true;
72 | if (value === min) {
73 | showPrevious = false;
74 | } else if (value === max) {
75 | showNext = false;
76 | }
77 |
78 | return (
79 |
80 | { showPrevious && (
81 |
82 | )}
83 |
84 |
85 |
86 | { showNext && (
87 |
88 | )}
89 |
90 | );
91 | }
92 | }
93 |
94 | export default Paginator;
--------------------------------------------------------------------------------
/web-app/src/components/filters/Slicer.css:
--------------------------------------------------------------------------------
1 | .slicer-body {
2 | padding-top: 5px;
3 | }
4 |
5 | .slicer-toggle-button {
6 | margin-top: 5px;
7 | background-color: #212121;
8 | color: #FFFFFF;
9 | border-radius: 4px;
10 | border: 0;
11 | height: 20px;
12 | line-height: 20px;
13 | outline: none;
14 | cursor: pointer;
15 | }
--------------------------------------------------------------------------------
/web-app/src/components/filters/Slicer.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
4 |
5 | import Checkbox from '../Checkbox/Checkbox';
6 | import './Slicer.css';
7 |
8 | class Slicer extends React.Component {
9 |
10 | constructor(props) {
11 | super(props);
12 | this.state = {
13 | isSelectAll: false,
14 | searchValue: ''
15 | };
16 | }
17 |
18 | static propTypes = {
19 | checkBoxes: PropTypes.array.isRequired,
20 | onChange: PropTypes.func.isRequired,
21 | readOnly: PropTypes.bool
22 | };
23 |
24 | toggleSelectAll = () => {
25 | const newIsSelectAll = !this.state.isSelectAll;
26 | const newCheckBoxes = [...this.props.checkBoxes];
27 | for (let i = 0; i < newCheckBoxes.length; i++) {
28 | newCheckBoxes[i].isChecked = newIsSelectAll;
29 | }
30 |
31 | this.setState(prevState => ({
32 | isSelectAll: !prevState.isSelectAll
33 | }));
34 |
35 | this.props.onChange(this.props.id, newCheckBoxes);
36 | }
37 |
38 | handleSearchValueChange = (event) => {
39 | const searchValue = event.target.value;
40 | this.setState({
41 | searchValue: searchValue
42 | });
43 | }
44 |
45 | handleCheckBoxChange = (name, isChecked) => {
46 | const newCheckBoxes = [...this.props.checkBoxes];
47 | const index = newCheckBoxes.findIndex(x => x.value === name);
48 | newCheckBoxes[index].isChecked = isChecked;
49 | this.props.onChange(this.props.id, newCheckBoxes);
50 | }
51 |
52 | render() {
53 | const {
54 | checkBoxes = [],
55 | readOnly = false
56 | } = this.props;
57 |
58 | const {
59 | searchValue,
60 | isSelectAll
61 | } = this.state;
62 |
63 | const checkBoxItems = [];
64 | for (let i = 0; i < checkBoxes.length; i++) {
65 | const {
66 | value,
67 | isChecked
68 | } = checkBoxes[i];
69 | if (!searchValue || (searchValue && value.includes(searchValue))) {
70 | checkBoxItems.push(
71 | (
72 |
80 | )
81 | )
82 | }
83 | }
84 | return (
85 |
86 | {/*
87 |
95 |
104 | */}
105 |
106 | {checkBoxItems}
107 |
108 |
109 | );
110 | }
111 | }
112 |
113 | export default Slicer;
114 |
--------------------------------------------------------------------------------
/web-app/src/components/processbar/Processbar.css:
--------------------------------------------------------------------------------
1 | .progress-container {
2 | background: #ddd;
3 | overflow: hidden;
4 | position: relative;
5 | width: inherit;
6 | height: 22px;
7 | }
8 |
9 | .progress-bar {
10 | background: #f80;
11 | left: 0;
12 | position: absolute;
13 | top: 0;
14 | height: 22px;
15 | }
16 |
17 | .progress-value {
18 | color: green;
19 | position: absolute;
20 | top: 0px;
21 | right: 8px;
22 | }
--------------------------------------------------------------------------------
/web-app/src/components/processbar/Processbar.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import './Processbar.css';
4 |
5 | class Processbar extends React.Component {
6 |
7 | constructor(props) {
8 | super(props);
9 | this.state = {
10 | }
11 | }
12 |
13 | static propTypes = {
14 | value: PropTypes.number.isRequired,
15 | percentage: PropTypes.number.isRequired,
16 | fontColor: PropTypes.string,
17 | barColor: PropTypes.string,
18 | backgroundColor: PropTypes.string
19 | };
20 |
21 | componentDidMount() {
22 | }
23 |
24 |
25 | render() {
26 | const {
27 | value,
28 | percentage,
29 | fontColor = 'green',
30 | barColor = '#f80',
31 | backgroundColor = '#ddd'
32 | } = this.props;
33 |
34 | const containerStyle = {
35 | backgroundColor: backgroundColor
36 | };
37 |
38 | const progressBarStyle = {
39 | width: percentage + '%',
40 | backgroundColor: barColor
41 | };
42 |
43 | const valueStyle = {
44 | color: fontColor
45 | };
46 |
47 | return (
48 |
52 | );
53 | }
54 | }
55 |
56 | export default Processbar;
57 |
--------------------------------------------------------------------------------
/web-app/src/components/table/Table.css:
--------------------------------------------------------------------------------
1 | /** ReactTable */
2 | .ReactTable {
3 | font-size: 15px !important;
4 | }
5 |
6 | .ReactTable .rt-table {
7 | background-color: #FFFFFF;
8 | }
9 |
10 | .ReactTable .rt-thead.-header {
11 | box-shadow: none !important;
12 | border-bottom: 1px solid #B3BAC5;
13 | }
14 |
15 | .ReactTable .rt-tbody .rt-td {
16 | padding: 5px 3px;
17 | }
18 |
19 | .ReactTable .-pagination {
20 | width: 400px !important;
21 | box-shadow: none !important;
22 | border-top: none !important;
23 | float: right;
24 | margin-right: 5px;
25 | font-size: 12px !important;
26 | padding: 0px !important;
27 | }
28 |
29 |
30 | .ReactTable .pagination-bottom {
31 | border-top: 1px solid #B3BAC5;
32 | background-color: #FFFFFF;
33 | height: 35px;
34 | }
35 |
36 | .ReactTable .-pagination .-previous {
37 | width: 60px !important;
38 | flex: none !important;
39 | height: 26px;
40 | margin-top: 4px;
41 | }
42 |
43 | .ReactTable .-pagination .-center {
44 | width: 270px !important;
45 | flex: none !important;
46 | }
47 |
48 | .ReactTable .-pagination .-next {
49 | width: 60px !important;
50 | flex: none !important;
51 | height: 26px;
52 | margin-top: 4px;
53 | }
54 |
55 | .ReactTable .-pagination .-pageInfo {
56 | margin: 3px 0px 3px 0px !important;
57 | }
58 |
59 | .ReactTable .-pagination .-pageSizeOptions {
60 | margin: 3px 0px 3px 0px !important;
61 | }
62 |
63 | .ReactTable .-pagination .-btn {
64 | border: 0;
65 | outline: none;
66 | cursor: pointer;
67 | color: #000000 !important;
68 | background-color: #FFFFFF !important;
69 | }
70 |
71 | .ReactTable .rt-tbody .rt-tr-group:last-child {
72 | border-bottom: solid 1px rgba(0,0,0,0.05) !important;
73 | }
74 |
75 | .ReactTable .rt-thead .rt-th.-sort-asc,
76 | .ReactTable .rt-thead .rt-td.-sort-asc {
77 | box-shadow: inset 0 3px 0 0 #0065FF;
78 | }
79 |
80 | .ReactTable .rt-thead .rt-th.-sort-desc,
81 | .ReactTable .rt-thead .rt-td.-sort-desc {
82 | box-shadow: inset 0 -3px 0 0 #0065FF;
83 | }
84 |
85 | .ReactTable .rt-resizer {
86 | z-index: auto !important;
87 | }
--------------------------------------------------------------------------------
/web-app/src/components/table/Table.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import ReactTable from 'react-table';
4 | import 'react-table/react-table.css';
5 | import './Table.css';
6 |
7 | class Table extends React.PureComponent {
8 |
9 | static propTypes = {
10 | data: PropTypes.array.isRequired,
11 | columns: PropTypes.array.isRequired,
12 | defaultPageSize: PropTypes.number,
13 | drillThrough: PropTypes.array,
14 | showPagination: PropTypes.bool,
15 | height: PropTypes.number,
16 | errorMsg: PropTypes.string
17 | };
18 |
19 | handleTdClick = (reportId, columnName, columnValue) => {
20 | this.props.onTableTdClick(reportId, columnName, columnValue);
21 | }
22 |
23 | render() {
24 | const {
25 | data = [],
26 | columns = [],
27 | drillThrough = [],
28 | defaultPageSize = 10,
29 | showPagination = true,
30 | height,
31 | errorMsg
32 | } = this.props;
33 |
34 | const columnHeaders = [];
35 | columns.forEach(column => {
36 | const columnName = column.name;
37 | const header = {
38 | Header: columnName,
39 | accessor: columnName
40 | };
41 | if (drillThrough.length > 0) {
42 | const index = drillThrough.findIndex(d => d.columnName === columnName);
43 | if (index !== -1) {
44 | const reportId = drillThrough[index].reportId;
45 | header.Cell = (props =>
46 | this.handleTdClick(reportId, columnName, props.value)}>
48 | {props.value}
49 |
50 | );
51 | }
52 | }
53 |
54 | columnHeaders.push(header);
55 | });
56 |
57 | if (errorMsg) {
58 | return (
59 | {errorMsg}
60 | );
61 | }
62 |
63 | if (data.length === 0 || columns.length === 0) {
64 | return (
65 | No data
66 | );
67 | }
68 |
69 | const pageSize = showPagination ? undefined : data.length;
70 | const style = height ? {
71 | height: height + 'px'
72 | } : {};
73 |
74 | return (
75 |
85 | );
86 | }
87 | }
88 |
89 | export default Table;
90 |
--------------------------------------------------------------------------------
/web-app/src/components/widgets/Card.css:
--------------------------------------------------------------------------------
1 | .card-container {
2 | display: flex;
3 | align-items: center;
4 | justify-content: center;
5 | height: 100%;
6 | }
7 |
8 | .card-value {
9 | text-overflow: ellipsis;
10 | white-space: nowrap;
11 | overflow: hidden;
12 | }
--------------------------------------------------------------------------------
/web-app/src/components/widgets/Card.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import './Card.css';
4 |
5 | class Card extends React.Component {
6 |
7 | static propTypes = {
8 | value: PropTypes.string.isRequired,
9 | fontSize: PropTypes.string,
10 | fontColor: PropTypes.string
11 | };
12 |
13 | render() {
14 | const {
15 | value,
16 | fontSize = 16,
17 | fontColor = '#000000'
18 | } = this.props;
19 |
20 | const valueStyle = {
21 | fontSize: fontSize + 'px',
22 | color: fontColor
23 | }
24 |
25 | return (
26 |
27 |
28 | {value}
29 |
30 |
31 | );
32 | }
33 | }
34 |
35 | export default Card;
--------------------------------------------------------------------------------
/web-app/src/components/widgets/Iframe.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | class Iframe extends React.Component {
5 |
6 | static propTypes = {
7 | src: PropTypes.string.isRequired,
8 | title: PropTypes.string.isRequired
9 | };
10 |
11 | render() {
12 | const {
13 | src,
14 | title
15 | } = this.props;
16 |
17 | const style = {
18 | width: '100%',
19 | height: '100%',
20 | border: '0px',
21 | display: 'block'
22 | }
23 |
24 | return (
25 |
26 | );
27 | }
28 | }
29 |
30 | export default Iframe;
--------------------------------------------------------------------------------
/web-app/src/components/widgets/ImageBox.css:
--------------------------------------------------------------------------------
1 | .image-original-scale {
2 | width: 100%;
3 | height: 100%;
4 | object-fit: contain;
5 | display: block;
6 | }
7 |
8 | .image-full {
9 | width: 100%;
10 | height: 100%;
11 | display: block;
12 | }
--------------------------------------------------------------------------------
/web-app/src/components/widgets/ImageBox.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import './ImageBox.css';
4 |
5 | class ImageBox extends React.Component {
6 |
7 | static propTypes = {
8 | src: PropTypes.string.isRequired,
9 | isFull: PropTypes.bool
10 | };
11 |
12 | render() {
13 | const {
14 | isFull = false,
15 | src
16 | } = this.props;
17 |
18 | const imageClass = isFull ? 'image-full' : 'image-original-scale';
19 |
20 | return (
21 |
26 | );
27 | }
28 | }
29 |
30 | export default ImageBox;
--------------------------------------------------------------------------------
/web-app/src/components/widgets/InnerHtml.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | class InnerHtml extends React.Component {
5 |
6 | static propTypes = {
7 | html: PropTypes.string.isRequired,
8 | style: PropTypes.object
9 | };
10 |
11 | render() {
12 | const {
13 | html,
14 | style
15 | } = this.props;
16 |
17 | const markup = {
18 | __html: html
19 | }
20 |
21 | return (
22 |
23 | );
24 | }
25 | }
26 |
27 | export default InnerHtml;
--------------------------------------------------------------------------------
/web-app/src/components/widgets/TextBox.css:
--------------------------------------------------------------------------------
1 | .text-box {
2 | word-wrap: break-word;
3 | overflow-y: auto;
4 | height: 100%;
5 | padding: 0px 5px;
6 | text-align: center;
7 | }
--------------------------------------------------------------------------------
/web-app/src/components/widgets/TextBox.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import './TextBox.css';
4 |
5 | class TextBox extends React.Component {
6 |
7 | static propTypes = {
8 | fontSize: PropTypes.string.isRequired,
9 | fontColor: PropTypes.string.isRequired,
10 | value: PropTypes.string.isRequired
11 | };
12 |
13 | render() {
14 | const {
15 | fontSize = 16,
16 | fontColor = '#000000',
17 | isLink = false,
18 | value,
19 | linkUrl
20 | } = this.props;
21 |
22 | const style = {
23 | fontSize: fontSize + 'px',
24 | color: fontColor
25 | }
26 |
27 | return (
28 |
29 | { isLink ?
30 | (
31 |
{value}
32 | ) :
33 | (
34 | value
35 | )
36 | }
37 |
38 | );
39 | }
40 | }
41 |
42 | export default TextBox;
--------------------------------------------------------------------------------
/web-app/src/i18n.js:
--------------------------------------------------------------------------------
1 | import i18n from 'i18next';
2 | import { initReactI18next } from 'react-i18next';
3 |
4 | import translationEN from './locales/en/translation';
5 | import translationZH from './locales/zh/translation';
6 | import translationES from './locales/es/translation';
7 | import translationFR from './locales/fr/translation';
8 |
9 | // the translations
10 | const resources = {
11 | en: {
12 | translation: translationEN
13 | },
14 | zh: {
15 | translation: translationZH
16 | },
17 | es: {
18 | translation: translationES
19 | },
20 | fr: {
21 | translation: translationFR
22 | }
23 | };
24 |
25 | i18n
26 | .use(initReactI18next)
27 | .init({
28 | resources: resources,
29 | lng: 'en',
30 | fallbackLng: 'en',
31 | interpolation: {
32 | escapeValue: false
33 | }
34 | });
35 |
36 | export default i18n;
--------------------------------------------------------------------------------
/web-app/src/index.css:
--------------------------------------------------------------------------------
1 | html,
2 | body,
3 | #root,
4 | .app {
5 | width: 100%;
6 | height: 100%;
7 | margin: 0;
8 | padding: 0;
9 | font-family: Tahoma, Geneva, sans-serif;
10 | color: #212121;
11 | -webkit-font-smoothing: antialiased;
12 | text-rendering: optimizeLegibility;
13 | -webkit-transition: all 0.15s ease;
14 | transition: all 0.15s ease;
15 | }
16 |
17 | textarea,
18 | input,
19 | button,
20 | select {
21 | font-family: inherit;
22 | font-size: inherit;
23 | box-shadow: rgba(50, 50, 93, 0.149) 0px 1px 3px, rgba(0, 0, 0, 0.0196) 0px 1px 0px;
24 | }
25 |
26 | textarea {
27 | resize: vertical;
28 | }
29 |
30 | /* Toastify */
31 | .Toastify__toast-body {
32 | white-space: pre-wrap;
33 | word-wrap: break-word;
34 | width: 260px;
35 | }
36 |
37 | /* Echarts */
38 | .echarts {
39 | width: 100% !important;
40 | height: 100% !important;
41 | }
42 |
43 | /* Utility */
44 | .display-flex {
45 | display: flex;
46 | }
47 |
48 | .row:after {
49 | content: "";
50 | display: table;
51 | clear: both;
52 | }
53 |
54 | .float-left {
55 | float: left;
56 | }
57 |
58 | .float-right {
59 | float: right;
60 | }
61 |
62 | .inline-block {
63 | display: inline-block;
64 | }
65 |
66 | .ellipsis {
67 | text-overflow: ellipsis;
68 | white-space: nowrap;
69 | overflow: hidden;
70 | }
71 |
72 | .display-block {
73 | display: block;
74 | }
75 |
76 | .display-none {
77 | display: none;
78 | }
79 |
80 | .word-break-all {
81 | word-break: break-all;
82 | }
83 |
84 | hr {
85 | display: block;
86 | height: 1px;
87 | border: 0;
88 | border-top: 1px solid #ccc;
89 | margin: 8px 0;
90 | padding: 0;
91 | }
92 |
93 | .bold {
94 | font-weight: 700;
95 | }
96 |
97 | .cursor-pointer {
98 | cursor: pointer;
99 | }
100 |
101 | .full-width {
102 | width: 100%;
103 | }
104 |
105 | /* Scroll bar */
106 | ::-webkit-scrollbar {
107 | width: 10px;
108 | height: 10px;
109 | }
110 |
111 | ::-webkit-scrollbar-thumb {
112 | background: #bdbdbd;
113 | border: 0px none transparent;
114 | }
115 |
116 | ::-webkit-scrollbar-thumb:hover {
117 | background: #bdbdbd;
118 | }
119 |
120 | ::-webkit-scrollbar-thumb:active {
121 | background: #bdbdbd;
122 | }
123 |
124 | ::-webkit-scrollbar-track {
125 | background: transparent;
126 | border: 0px none transparent;
127 | }
128 |
129 | ::-webkit-scrollbar-track:hover {
130 | background: transparent;
131 | }
132 |
133 | ::-webkit-scrollbar-track:active {
134 | background: transparent;
135 | }
136 |
137 | ::-webkit-scrollbar-corner {
138 | background: transparent;
139 | }
--------------------------------------------------------------------------------
/web-app/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 | import { BrowserRouter } from "react-router-dom";
5 | import './i18n';
6 | import './index.css';
7 |
8 | ReactDOM.render(
9 |
10 |
11 | ,
12 | document.getElementById('root')
13 | );
14 |
15 |
--------------------------------------------------------------------------------
/web-app/src/views/Login/Login.css:
--------------------------------------------------------------------------------
1 | .login-view {
2 | }
3 |
4 | .login-input {
5 | padding: 7px 10px !important;
6 | border: 2px solid #FFFFFF !important;
7 | }
8 |
9 | .login-input:focus {
10 | border: 2px solid #bdbdbd !important;
11 | }
12 |
13 | .login-panel {
14 | padding: 15px;
15 | position: fixed;
16 | top: 50%;
17 | left: 50%;
18 | width: 300px;
19 | -webkit-transform: translate(-50%, -50%);
20 | transform: translate(-50%, -50%);
21 | }
22 |
23 | .login-panel-body {
24 | padding: 15px;
25 | background-color:#eeeeee;
26 | border-radius: 5px;
27 | }
28 |
29 | .login-app-title {
30 | font-size: 35px;
31 | text-align: center;
32 | font-weight: 700;
33 | margin-bottom: 25px;
34 | color: #0065FF;
35 | }
36 |
37 | .login-button {
38 | width: 100%;
39 | }
40 |
41 | .login-error-msg {
42 | text-align: center;
43 | margin-bottom: 5px;
44 | color: #ef5350;
45 | font-weight: bold;
46 | }
47 |
48 | .version-number {
49 | position: absolute;
50 | bottom: 5px;
51 | right: 10px;
52 | font-size: 12px;
53 | }
--------------------------------------------------------------------------------
/web-app/src/views/PageNotFound.js:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react';
3 | import { Link } from "react-router-dom";
4 |
5 | class PageNotFound extends React.Component {
6 | render() {
7 | return (
8 |
9 | PageNotFound
10 | Login
11 |
12 | )
13 | };
14 | }
15 |
16 | export default PageNotFound;
--------------------------------------------------------------------------------
/web-app/src/views/Report/ComponentEditPanel.css:
--------------------------------------------------------------------------------
1 |
2 | .schema-table-title {
3 | padding: 4px 8px;
4 | cursor: pointer;
5 | margin-bottom: 3px;
6 | border: 0;
7 | outline: 0;
8 | border-radius: 4px;
9 | background-color: #FFFFFF;
10 | box-shadow: rgba(50, 50, 93, 0.149) 0px 1px 3px, rgba(0, 0, 0, 0.0196) 0px 1px 0px;
11 | font-weight: 700;
12 | }
13 |
14 | .schema-column-row {
15 | padding: 4px 8px 4px 20px;
16 | }
17 |
18 | .schema-column-type {
19 | color: #8993A4;
20 | }
21 |
22 | .tag-label {
23 | padding: 2px 12px;
24 | background-color: #8993A4;
25 | color: #FFFFFF;
26 | display: inline-block;
27 | }
28 |
29 | .grid-label {
30 | line-height: 32px;
31 | margin-right: 5px;
32 | }
33 |
34 | .grid-input {
35 | width: 50px;
36 | margin-right: 8px;
37 | text-align: right;
38 | }
--------------------------------------------------------------------------------
/web-app/src/views/Report/ComponentViewPanel.css:
--------------------------------------------------------------------------------
1 | .report-component-style-panel {
2 | position: fixed;
3 | top: 40px;
4 | left: 0px;
5 | bottom: 0px;
6 | width: 200px;
7 | border: 0;
8 | outline: 0;
9 | z-index: 30;
10 | overflow-y: auto;
11 | background-color: #4B4B4B;
12 | color: #DFDFDF;
13 | z-index: 70;
14 | }
--------------------------------------------------------------------------------
/web-app/src/views/Report/Report.css:
--------------------------------------------------------------------------------
1 | .report-sidebar {
2 | position: absolute;
3 | top: 0;
4 | left: 0;
5 | bottom: 0;
6 | width: 199px;
7 | overflow-y: auto;
8 | border-right: 1px solid #ccc;
9 | background-color: #efefef;
10 | }
11 |
12 | .report-content {
13 | position: absolute;
14 | top: 0;
15 | left: 200px;
16 | right: 0;
17 | bottom: 0;
18 | background-color: #FFFFFF;
19 | overflow: auto;
20 | }
21 |
22 | .report-menu-panel {
23 | padding: 5px 10px;
24 | }
25 |
26 | .report-menu-item {
27 | border-left: 3px solid #EBECF0;
28 | margin: 3px 0px;
29 | padding: 3px 6px;
30 | color: #8993A4;
31 | cursor: pointer;
32 | }
33 |
34 | .report-menu-item:hover {
35 | border-left: 3px solid #0065FF;
36 | }
37 |
38 | .report-menu-item-active {
39 | border-left: 3px solid #0065FF;
40 | color: #0065FF;
41 | font-weight: 700;
42 | }
43 |
44 | .report-content-component-panel {
45 | position: absolute;
46 | top: 0;
47 | left: 0;
48 | bottom: 0;
49 | right: 0;
50 | overflow: auto;
51 | padding: 0px 10px 10px 20px;
52 | }
53 |
54 | .report-drillthrough-name {
55 | padding: 6px 0px 6px 14px;
56 | font-size: 20px;
57 | }
58 |
59 | .report-name {
60 | padding: 6px 14px;
61 | font-size: 20px;
62 | }
63 |
64 | .report-name-input {
65 | margin-left: 10px;
66 | }
67 |
68 | .report-edit-component-dialog {
69 | left: 50%;
70 | width: 800px;
71 | top: 20px;
72 | bottom: 20px;
73 | margin-left: -400px;
74 | }
75 |
76 | .report-edit-filter-dialog {
77 | left: 50%;
78 | width: 1000px;
79 | top: 20px;
80 | bottom: 20px;
81 | margin-left: -500px;
82 | }
83 |
84 | .empty-report {
85 | margin-top: 20px;
86 | text-align: center;
87 | color:#0065FF;
88 | font-size: 20px;
89 | font-weight: 700;
90 | }
91 |
92 | .report-side-panel {
93 | position: fixed;
94 | top: 40px;
95 | left: 0px;
96 | bottom: 0px;
97 | width: 200px;
98 | border: 0;
99 | outline: 0;
100 | overflow-y: auto;
101 | background-color: #4B4B4B;
102 | color: #DFDFDF;
103 | z-index: 60;
104 | }
105 |
106 | .project-row {
107 | margin: 5px 0px;
108 | padding: 3px 0px;
109 | cursor: pointer;
110 | }
111 |
112 | .exporting-panel {
113 | position: fixed;
114 | top: 50%;
115 | left: 50%;
116 | width: 160px;
117 | height: 90px;
118 | margin-top: -45px;
119 | margin-left: -80px;
120 | text-align: center;
121 | }
122 |
123 | .exporting-panel-title {
124 | font-size: 22px;
125 | font-weight: 700;
126 | margin: 0px 0px 15px 0px;
127 | color: #FFFFFF;
128 | }
129 |
130 | .exporting-overlay {
131 | background: rgba(0, 0, 0, 0.6);
132 | position: fixed;
133 | top: 0;
134 | left: 0;
135 | right: 0;
136 | bottom: 0;
137 | z-index: 150;
138 | }
--------------------------------------------------------------------------------
/web-app/src/views/ReportFullScreenView.js:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react';
3 | import ReportEditView from './Report/ReportEditView';
4 |
5 | class ReportFullScreenView extends React.Component {
6 |
7 | render() {
8 | const href = window.location.href;
9 |
10 | return (
11 |
12 |
13 |
14 | );
15 | };
16 | }
17 |
18 | export default ReportFullScreenView;
19 |
--------------------------------------------------------------------------------
/web-app/src/views/Studio/ReactSelectHelper.js:
--------------------------------------------------------------------------------
1 | export const CUSTOM_STYLE = {
2 | control: (provided, state) => ({
3 | ...provided,
4 | background: '#ffffff',
5 | borderColor: state.isSelected ? '#bdbdbd' : '#e0e0e0',
6 | minHeight: '32px',
7 | height: '32px',
8 | boxShadow: 'rgba(50, 50, 93, 0.149) 0px 1px 3px, rgba(0, 0, 0, 0.0196) 0px 1px 0px',
9 | '&:hover': {
10 | borderColor: state.isFocused ? '#bdbdbd' : '#e0e0e0'
11 | }
12 | }),
13 | valueContainer: (provided, state) => ({
14 | ...provided,
15 | height: '32px',
16 | padding: '0 6px'
17 | }),
18 | indicatorsContainer: (provided, state) => ({
19 | ...provided,
20 | height: '32px',
21 | }),
22 | singleValue: (provided, state) => ({
23 | ...provided,
24 | top: '45%'
25 | }),
26 | placeholder: (provided, state) => ({
27 | ...provided,
28 | top: '45%'
29 | }),
30 | menu: (provided, state) => ({
31 | ...provided,
32 | marginTop: '0px',
33 | zIndex: '100'
34 | }),
35 | option: (provided, state) => ({
36 | ...provided,
37 | color: state.isSelected ? '#FFFFFF' : '#212121',
38 | backgroundColor: state.isSelected ? '#212121' : '#FFFFFF',
39 | padding: '6px 8px',
40 | '&:hover': {
41 | backgroundColor: '#C1C7D0',
42 | color: '#FFFFFF'
43 | }
44 | })
45 | }
--------------------------------------------------------------------------------
/web-app/src/views/Studio/SchemaPanel.css:
--------------------------------------------------------------------------------
1 | .schema-side-panel {
2 | position: absolute;
3 | top: 0;
4 | right: 0;
5 | bottom: 0;
6 | z-index: 500;
7 | width: 449px;
8 | min-width: 249px;
9 | overflow-y: auto;
10 | border-left: 1px solid #ccc;
11 | background-color: #efefef;
12 | padding: 5px;
13 | }
14 |
15 | .schema-title-row {
16 | padding: 4px 8px;
17 | cursor: pointer;
18 | margin-bottom: 3px;
19 | border: 0;
20 | outline: 0;
21 | border-radius: 4px;
22 | background-color: #FFFFFF;
23 | box-shadow: rgba(50, 50, 93, 0.149) 0px 1px 3px, rgba(0, 0, 0, 0.0196) 0px 1px 0px;
24 | }
25 |
26 | .schema-column-row-type {
27 | color: #8993A4;
28 | }
--------------------------------------------------------------------------------
/web-app/src/views/Studio/ScrollTabPanel.css:
--------------------------------------------------------------------------------
1 | .dynamic-tab-container {
2 | box-sizing: border-box;
3 | height: 40px;
4 | position: relative;
5 | border-bottom: 1px solid #ccc;
6 | margin-top: 3px;
7 | }
8 |
9 | .dynamic-tab-button-group {
10 | position: absolute;
11 | top: 0;
12 | left: 8px;
13 | width: 120px;
14 | line-height: 40px;
15 | height: 40px;
16 | }
17 |
18 | .dynamic-tab-droppable-container {
19 | position: absolute;
20 | top: 0;
21 | left: 120px;
22 | right: 0px;
23 | }
24 |
25 | .dynamic-tab-content-container {
26 | flex: 1 0 auto;
27 | display: flex;
28 | flex-direction: row;
29 | }
30 |
31 | .dynamic-tab-select-item {
32 | display: flex;
33 | height: 33px;
34 | line-height: 33px;
35 | }
36 |
37 | .dynamic-tab-select-value {
38 | width: 100px;
39 | text-overflow: ellipsis;
40 | white-space: nowrap;
41 | overflow: hidden;
42 | }
43 |
44 | .dynamic-tab-close-button {
45 | border-radius: 4px;
46 | height: 33px;
47 | width: 28px;
48 | outline: none;
49 | padding: 0px !important;
50 | cursor: pointer;
51 | box-shadow: none !important;
52 | border: 0 !important;
53 | background-color: transparent !important;
54 | }
--------------------------------------------------------------------------------
/web-app/src/views/Studio/Studio.css:
--------------------------------------------------------------------------------
1 | .studio-container {
2 | box-sizing: border-box;
3 | display: flex;
4 | flex-direction: row;
5 | height: 100%;
6 | background-color: #FFFFFF;
7 | }
8 |
9 | .studio-menu-sidebar {
10 | width: 199px;
11 | min-width: 199px;
12 | overflow-y: auto;
13 | border-right: 1px solid #ccc;
14 | background-color: #efefef;
15 | }
16 |
17 | .studio-property-sidebar {
18 | width: 199px;
19 | min-width: 199px;
20 | overflow-y: auto;
21 | border-right: 1px solid #ccc;
22 | background-color: #efefef;
23 | }
24 |
25 | .studio-property-panel {
26 | border-bottom: 1px solid #ccc;
27 | background-color: #FFFFFF;
28 | padding: 5px 8px 0px 8px;
29 | }
30 |
31 | .studio-body {
32 | flex: 1 1 auto;
33 | display: flex;
34 | flex-direction: column;
35 | overflow: hidden;
36 | }
37 |
38 | .studio-editor-container {
39 | height: 320px;
40 | }
41 |
42 | .studio-result-control-container {
43 | border-top: 1px solid #ccc;
44 | border-bottom: 1px solid #ccc;
45 | padding: 5px 8px;
46 | display: flex;
47 | justify-content: space-between;
48 | }
49 |
50 | .studio-result-container {
51 | overflow-y: auto;
52 | }
53 |
54 | .studio-datasource-container {
55 | padding: 5px 8px;
56 | border-bottom: 1px solid #ccc;
57 | display: flex;
58 | }
59 |
60 | .studio-datasource-select {
61 | width: 300px;
62 | }
63 |
64 | .studio-query-menu-item {
65 | border-left: 3px solid #EBECF0;
66 | margin: 5px 0px;
67 | padding: 3px 3px 3px 8px;
68 | font-weight: 700;
69 | color: #8993A4;
70 | cursor: pointer;
71 | }
72 |
73 | .studio-query-menu-item:hover {
74 | border-left: 3px solid #0065FF;
75 | }
76 |
77 | .studio-query-menu-item-active {
78 | border-left: 3px solid #0065FF;
79 | color: #0065FF;
80 | }
--------------------------------------------------------------------------------
/web-app/src/views/Workspace.css:
--------------------------------------------------------------------------------
1 | .workspace-name {
2 | display: block;
3 | color: #0065FF;
4 | padding: 7px 16px;
5 | font-size: 22px;
6 | font-weight: 700;
7 | float: left;
8 | margin-right: 6px;
9 | }
10 |
11 | .workspace-nav {
12 | width: 100%;
13 | height: 40px;
14 | background-color: #212121;
15 | box-sizing: border-box;
16 | }
17 |
18 | .workspace-nav-menu {
19 | list-style-type: none;
20 | margin: 0;
21 | padding: 0;
22 | overflow: hidden;
23 | float: left;
24 | }
25 |
26 | .workspace-nav-menu-text {
27 | margin-left: 6px;
28 | }
29 |
30 | .workspace-nav-menu-item {
31 | display: inline-block;
32 | list-style: none;
33 | font-size: 16px;
34 | }
35 |
36 | .workspace-nav-menu-item-value {
37 | display: block;
38 | color: #FFFFFF;
39 | padding: 11px 9px 10px 9px;
40 | cursor: pointer;
41 | }
42 |
43 | .workspace-nav-menu-item-dropdown {
44 | position: absolute;
45 | z-index: 1;
46 | background-color: #212121;
47 | min-width: 100px;
48 | }
49 |
50 | .workspace-nav-menu-item-value:hover {
51 | background-color: #003ccb;
52 | color: #FFFFFF !important;
53 | }
54 |
55 | .menu-item-active {
56 | background-color: #0065FF;
57 | color: #FFFFFF !important;
58 | }
59 |
60 | .workspace-content {
61 | position: fixed;
62 | top: 40px;
63 | left: 0px;
64 | right: 0;
65 | bottom: 0;
66 | background-color: #efefef;
67 | overflow-x: hidden;
68 | overflow-y: auto;
69 | }
70 |
71 | .workspace-account-menu {
72 | float: right;
73 | }
74 |
75 | .workspace-account-button {
76 | display: inline-block;
77 | color: #FFFFFF;
78 | padding: 11px 10px 10px 10px;
79 | text-decoration: none;
80 | cursor: pointer;
81 | }
82 |
83 | .workspace-account-button:hover {
84 | background-color: #003ccb;
85 | color: #FFFFFF !important;
86 | }
87 |
88 | .workspace-account-dropdown {
89 | position: absolute;
90 | right: 0px;
91 | z-index: 1;
92 | background-color: #212121;
93 | width: 80px;
94 | }
95 |
96 | .workspace-dropdown-button {
97 | color: #FFFFFF;
98 | cursor: pointer;
99 | padding: 8px 12px;
100 | }
101 |
102 | .workspace-dropdown-button:hover {
103 | background-color: #003ccb;
104 | color: #FFFFFF !important;
105 | }
--------------------------------------------------------------------------------