Compare commits

...

181 Commits

Author SHA1 Message Date
AuroraLS3 c8fee67f10 Update versions.txt 5.6 build 2883 2024-05-26 15:08:30 +00:00
Aurora Lahtela bea0ef85b3 Fix join address validator test 2024-05-25 10:39:14 +03:00
Aurora Lahtela 8309f8725e Add config options for enabling and filtering join address gathering
Affects issues:
- Close #3631
2024-05-25 10:22:54 +03:00
dependabot[bot] 8e6befc953
Bump swagger-ui from 5.17.10 to 5.17.12 in /Plan/react/dashboard (#3636)
Bumps [swagger-ui](https://github.com/swagger-api/swagger-ui) from 5.17.10 to 5.17.12.
- [Release notes](https://github.com/swagger-api/swagger-ui/releases)
- [Changelog](https://github.com/swagger-api/swagger-ui/blob/master/.releaserc)
- [Commits](https://github.com/swagger-api/swagger-ui/compare/v5.17.10...v5.17.12)

---
updated-dependencies:
- dependency-name: swagger-ui
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-25 10:19:01 +03:00
Aurora Lahtela 41ae9a3a70 Add plan_server_uptime placeholder
Supports :server parameter

Affects issues:
- Close #3647
2024-05-25 09:43:54 +03:00
dependabot[bot] 18a154e6b0
Bump highcharts from 11.4.1 to 11.4.3 in /Plan/react/dashboard (#3637)
Bumps [highcharts](https://github.com/highcharts/highcharts-dist) from 11.4.1 to 11.4.3.
- [Commits](https://github.com/highcharts/highcharts-dist/compare/v11.4.1...v11.4.3)

---
updated-dependencies:
- dependency-name: highcharts
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-25 09:37:39 +03:00
dependabot[bot] 3e30340aa8
Bump react-i18next from 14.1.1 to 14.1.2 in /Plan/react/dashboard (#3640)
Bumps [react-i18next](https://github.com/i18next/react-i18next) from 14.1.1 to 14.1.2.
- [Changelog](https://github.com/i18next/react-i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/react-i18next/compare/v14.1.1...v14.1.2)

---
updated-dependencies:
- dependency-name: react-i18next
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-25 09:37:30 +03:00
dependabot[bot] 4b67a89d8c
Bump @vitejs/plugin-react from 4.2.1 to 4.3.0 in /Plan/react/dashboard (#3633)
Bumps [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react) from 4.2.1 to 4.3.0.
- [Release notes](https://github.com/vitejs/vite-plugin-react/releases)
- [Changelog](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite-plugin-react/commits/v4.3.0/packages/plugin-react)

---
updated-dependencies:
- dependency-name: "@vitejs/plugin-react"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-25 09:37:03 +03:00
dependabot[bot] c5513cd5db
Bump axios from 1.6.8 to 1.7.2 in /Plan/react/dashboard (#3634)
Bumps [axios](https://github.com/axios/axios) from 1.6.8 to 1.7.2.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.6.8...v1.7.2)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-25 09:36:47 +03:00
dependabot[bot] cde66f1981
Bump @fortawesome/react-fontawesome in /Plan/react/dashboard (#3635)
Bumps [@fortawesome/react-fontawesome](https://github.com/FortAwesome/react-fontawesome) from 0.2.1 to 0.2.2.
- [Release notes](https://github.com/FortAwesome/react-fontawesome/releases)
- [Changelog](https://github.com/FortAwesome/react-fontawesome/blob/0.2.x/CHANGELOG.md)
- [Commits](https://github.com/FortAwesome/react-fontawesome/compare/0.2.1...0.2.2)

---
updated-dependencies:
- dependency-name: "@fortawesome/react-fontawesome"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-25 09:36:26 +03:00
dependabot[bot] 4481c6411e
Bump @fullcalendar/core from 6.1.11 to 6.1.13 in /Plan/react/dashboard (#3638)
Bumps [@fullcalendar/core](https://github.com/fullcalendar/fullcalendar/tree/HEAD/packages/core) from 6.1.11 to 6.1.13.
- [Release notes](https://github.com/fullcalendar/fullcalendar/releases)
- [Changelog](https://github.com/fullcalendar/fullcalendar/blob/main/CHANGELOG.md)
- [Commits](https://github.com/fullcalendar/fullcalendar/commits/v6.1.13/packages/core)

---
updated-dependencies:
- dependency-name: "@fullcalendar/core"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-25 09:36:08 +03:00
dependabot[bot] 99d22185bb
Bump i18next from 23.11.4 to 23.11.5 in /Plan/react/dashboard (#3639)
Bumps [i18next](https://github.com/i18next/i18next) from 23.11.4 to 23.11.5.
- [Release notes](https://github.com/i18next/i18next/releases)
- [Changelog](https://github.com/i18next/i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/i18next/compare/v23.11.4...v23.11.5)

---
updated-dependencies:
- dependency-name: i18next
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-25 09:35:46 +03:00
dependabot[bot] 26fd2992a8
Bump i18next-http-backend from 2.5.1 to 2.5.2 in /Plan/react/dashboard (#3641)
Bumps [i18next-http-backend](https://github.com/i18next/i18next-http-backend) from 2.5.1 to 2.5.2.
- [Changelog](https://github.com/i18next/i18next-http-backend/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/i18next-http-backend/compare/v2.5.1...v2.5.2)

---
updated-dependencies:
- dependency-name: i18next-http-backend
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-25 09:35:32 +03:00
dependabot[bot] 5472144f74
Bump react-dom from 18.2.0 to 18.3.1 in /Plan/react/dashboard (#3642)
Bumps [react-dom](https://github.com/facebook/react/tree/HEAD/packages/react-dom) from 18.2.0 to 18.3.1.
- [Release notes](https://github.com/facebook/react/releases)
- [Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/react/commits/v18.3.1/packages/react-dom)

---
updated-dependencies:
- dependency-name: react-dom
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-25 09:35:21 +03:00
dependabot[bot] dddb9186b2
Bump com.google.code.gson:gson from 2.10.1 to 2.11.0 in /Plan (#3643)
Bumps [com.google.code.gson:gson](https://github.com/google/gson) from 2.10.1 to 2.11.0.
- [Release notes](https://github.com/google/gson/releases)
- [Changelog](https://github.com/google/gson/blob/main/CHANGELOG.md)
- [Commits](https://github.com/google/gson/compare/gson-parent-2.10.1...gson-parent-2.11.0)

---
updated-dependencies:
- dependency-name: com.google.code.gson:gson
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-25 09:34:22 +03:00
dependabot[bot] c5f811f99e
Bump org.apache.commons:commons-compress from 1.26.1 to 1.26.2 in /Plan (#3644)
Bumps org.apache.commons:commons-compress from 1.26.1 to 1.26.2.

---
updated-dependencies:
- dependency-name: org.apache.commons:commons-compress
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-25 09:34:11 +03:00
dependabot[bot] b2deda8a28
Bump org.mariadb.jdbc:mariadb-java-client from 3.3.3 to 3.4.0 in /Plan (#3645)
Bumps [org.mariadb.jdbc:mariadb-java-client](https://github.com/mariadb-corporation/mariadb-connector-j) from 3.3.3 to 3.4.0.
- [Release notes](https://github.com/mariadb-corporation/mariadb-connector-j/releases)
- [Changelog](https://github.com/mariadb-corporation/mariadb-connector-j/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mariadb-corporation/mariadb-connector-j/compare/3.3.3...3.4.0)

---
updated-dependencies:
- dependency-name: org.mariadb.jdbc:mariadb-java-client
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-25 09:34:01 +03:00
Aurora Lahtela edb871661c Truncate access address in case it is spoofed
Fixes an exception on inserting too long IP address
2024-05-24 17:24:32 +03:00
Aurora Lahtela ad98e28e4c Check permissions when performing tab completion
- Fixes advisory GHSA-cchm-2r9h-xvhv
2024-05-23 21:08:44 +03:00
Aurora Lahtela 365ea2d333 Lower webserver disabled log message from warn to info level 2024-05-18 19:16:30 +03:00
张宇衡 fefbd9ae23
Folia support added by ZhangYuheng (#3617)
* folia support finished
* add Contributors
* try to use reflection to load class
* finish the compatible for java11 and java 17+ (folia)
* change pal version to 5.2.0, due to the merge of folia-layer.
now this can be compiled by CI-CD(workflow)
* separate folia pal to standalone gradle module
so javadoc gen may success
* made folia module skip javadoc compile
* take advise from AnttiMK. Thank u :)
* make folia pal with runtimeOnly
* extend FileWatcherTest time await up to 5 sec

Affects issues:
- Close #2962
2024-05-18 18:52:39 +03:00
dependabot[bot] 5e0780be1a
Bump ejs from 3.1.7 to 3.1.10 in /Plan/react/dashboard (#3591)
Bumps [ejs](https://github.com/mde/ejs) from 3.1.7 to 3.1.10.
- [Release notes](https://github.com/mde/ejs/releases)
- [Commits](https://github.com/mde/ejs/compare/v3.1.7...v3.1.10)

---
updated-dependencies:
- dependency-name: ejs
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-18 10:46:30 +03:00
Aurora Lahtela 3dc6ac5afa Fix rate limiter failing /manage page test
Poor implementation makes /manage/groups page request /v1/permissions n times,
where n is amount of groups. Since each visibility test registers a new group,
there are a lot of them when opening the manage page, leading to tripping the
rate-limiter
2024-05-18 10:46:49 +03:00
Aurora Lahtela 8a16455e95 Update highcharts 2024-05-18 09:36:39 +03:00
Aurora Lahtela de75d3c56f Fix className is undefined error for FontAwesome 2024-05-18 09:32:14 +03:00
Aurora Lahtela 7de0464137 Update dependencies 2024-05-17 20:52:44 +03:00
jhqwqmc 8b2f32bb7d
Update locale_CN.yml (#3616) 2024-05-13 21:55:21 +03:00
Aurora Lahtela ca9870d9fe Fix CorrectWrongCharacterEncodingPatch failing on mysql.user table being a view 2024-05-11 18:58:22 +03:00
Aurora Lahtela 9e25f2b26c Sanitize and validate more join address variations
- Added Data_gathering.Preserve_invalid_join_addresses to allow overriding this behavior.

Affects issues:
- Fixed #3545
2024-04-27 10:08:18 +03:00
Aurora Lahtela 24a8c75b67 Add support for configuring the plugin using environment variables.
Examples:
- Plugin.ServerName -> PLAN_PLUGIN_SERVERNAME
- Database.MySQL.Password -> PLAN_DATABASE_MYSQL_PASSWORD

Affects issues:
- Close #1353
- #1991
2024-04-21 20:35:41 +03:00
Sniper_TVmc 132fa2f919
Update locale_FR.yml (#3578) 2024-04-21 09:25:28 +03:00
Aurora Lahtela 7f268d9a07
Update versions.txt 2024-04-20 20:42:27 +03:00
Aurora Lahtela be8c8951c9 Update jitpack.yml to use JDK 21 2024-04-20 10:34:03 +03:00
AuroraLS3 e630db1b71 Update versions.txt 5.6 DEV build 2850 2024-04-20 07:26:27 +00:00
Aurora Lahtela 0d5d9adb77 Bump Extension-PlaceholderAPI to R1.6
Allows configuring invalid placeholder values that should not overwrite existing values.

Affects issues:
- Fixed #3566
2024-04-20 10:12:15 +03:00
jhqwqmc 590e0445cb
Update locale CN by jhqwqmc (#3567) 2024-04-20 10:04:24 +03:00
Aurora Lahtela bd4108a367 Update dependencies
- @testing-library/react
- i18next
- i18next-http-backend
- sass
- swagger-ui
- org.apache.commons.commons-text
- slf4j
2024-04-20 10:02:35 +03:00
Drex faa911e232
Update to 1.20.5 Fabric by DrexHD (#3424)
* Update to 24w03b
* Update to 1.20.5-rc2
* Bump minecraft dependency
2024-04-20 09:48:33 +03:00
dependabot[bot] 6776c79a14
Bump org.sonarqube from 4.4.1.3373 to 5.0.0.4638 in /Plan (#3542)
Bumps org.sonarqube from 4.4.1.3373 to 5.0.0.4638.

---
updated-dependencies:
- dependency-name: org.sonarqube
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-07 14:40:15 +03:00
dependabot[bot] 13d168a593
Bump daggerVersion from 2.51 to 2.51.1 in /Plan (#3555)
Bumps `daggerVersion` from 2.51 to 2.51.1.

Updates `com.google.dagger:dagger` from 2.51 to 2.51.1
- [Release notes](https://github.com/google/dagger/releases)
- [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/dagger/compare/dagger-2.51...dagger-2.51.1)

Updates `com.google.dagger:dagger-compiler` from 2.51 to 2.51.1
- [Release notes](https://github.com/google/dagger/releases)
- [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/dagger/compare/dagger-2.51...dagger-2.51.1)

---
updated-dependencies:
- dependency-name: com.google.dagger:dagger
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: com.google.dagger:dagger-compiler
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-07 13:41:33 +03:00
Aurora Lahtela 634d6f2a77 Update javascript dependencies
- font awesome
- sass
- swagger-ui
2024-04-07 13:40:41 +03:00
Aurora Lahtela 74ae2dcf40 Fix server allowlist bounces not being exported 2024-04-07 11:18:38 +03:00
Aurora Lahtela f40e1498c1
3268/redesign join address visualization (#3558)
- Join address pie removed
- Join address group mechanism added
  - User can select multiple addresses for each group
  - User can rename each group to their liking
  - The groups are stored in preferences so that user doesn't need to add them back every time
- Use the join address group mechanism for time series of Join Addresses
- Use the join address group mechanism for Player Retention
- Small improvement to retention graph: Show multiple labels
- Small improvement to site clock: Can now hover to show actual date

Affected issues:
- Close #3268
2024-04-07 11:13:18 +03:00
dependabot[bot] b6f68936cf
Bump org.seleniumhq.selenium:selenium-java in /Plan (#3554)
Bumps [org.seleniumhq.selenium:selenium-java](https://github.com/SeleniumHQ/selenium) from 4.18.1 to 4.19.1.
- [Release notes](https://github.com/SeleniumHQ/selenium/releases)
- [Commits](https://github.com/SeleniumHQ/selenium/commits)

---
updated-dependencies:
- dependency-name: org.seleniumhq.selenium:selenium-java
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-05 14:34:01 +03:00
dependabot[bot] 55799eafa0
Bump vite from 5.2.2 to 5.2.8 in /Plan/react/dashboard (#3548)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.2.2 to 5.2.8.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.2.8/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-05 14:31:45 +03:00
Aurora Lahtela 8c36f318c7 Prevent duplicate key exception from being thrown entirely
Affects issues:
- Fixed #3543
2024-03-30 10:11:15 +02:00
Aurora Lahtela 49269d3aab Attempt to fix duplicate key issue in UpdateWebPermissionsPatch
Affects issues:
- #3543
2024-03-30 09:48:19 +02:00
dependabot[bot] 95a20b54a3
Bump @testing-library/react in /Plan/react/dashboard (#3528)
Bumps [@testing-library/react](https://github.com/testing-library/react-testing-library) from 14.2.1 to 14.2.2.
- [Release notes](https://github.com/testing-library/react-testing-library/releases)
- [Changelog](https://github.com/testing-library/react-testing-library/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testing-library/react-testing-library/compare/v14.2.1...v14.2.2)

---
updated-dependencies:
- dependency-name: "@testing-library/react"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-23 13:25:36 +02:00
dependabot[bot] 2b9314a104
Bump sass from 1.71.1 to 1.72.0 in /Plan/react/dashboard (#3520)
Bumps [sass](https://github.com/sass/dart-sass) from 1.71.1 to 1.72.0.
- [Release notes](https://github.com/sass/dart-sass/releases)
- [Changelog](https://github.com/sass/dart-sass/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sass/dart-sass/compare/1.71.1...1.72.0)

---
updated-dependencies:
- dependency-name: sass
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-23 13:25:29 +02:00
dependabot[bot] 5a80c6482b
Bump io.swagger.core.v3.swagger-gradle-plugin in /Plan (#3533)
Bumps io.swagger.core.v3.swagger-gradle-plugin from 2.2.20 to 2.2.21.

---
updated-dependencies:
- dependency-name: io.swagger.core.v3.swagger-gradle-plugin
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-23 13:09:06 +02:00
dependabot[bot] 92f887b4a0
Bump react-bootstrap from 2.10.1 to 2.10.2 in /Plan/react/dashboard (#3529)
Bumps [react-bootstrap](https://github.com/react-bootstrap/react-bootstrap) from 2.10.1 to 2.10.2.
- [Release notes](https://github.com/react-bootstrap/react-bootstrap/releases)
- [Changelog](https://github.com/react-bootstrap/react-bootstrap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/react-bootstrap/react-bootstrap/compare/v2.10.1...v2.10.2)

---
updated-dependencies:
- dependency-name: react-bootstrap
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-23 13:08:53 +02:00
dependabot[bot] 5af89eb787
Bump swaggerVersion from 2.2.20 to 2.2.21 in /Plan (#3532)
Bumps `swaggerVersion` from 2.2.20 to 2.2.21.

Updates `io.swagger.core.v3:swagger-core-jakarta` from 2.2.20 to 2.2.21

Updates `io.swagger.core.v3:swagger-jaxrs2-jakarta` from 2.2.20 to 2.2.21

---
updated-dependencies:
- dependency-name: io.swagger.core.v3:swagger-core-jakarta
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: io.swagger.core.v3:swagger-jaxrs2-jakarta
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-23 13:07:54 +02:00
dependabot[bot] d5fc213523
Bump vite from 5.1.5 to 5.2.2 in /Plan/react/dashboard (#3531)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.1.5 to 5.2.2.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/create-vite@5.2.2/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-23 13:07:38 +02:00
dependabot[bot] e4a43af3ef
Bump org.apache.commons:commons-compress from 1.26.0 to 1.26.1 in /Plan (#3522)
Bumps org.apache.commons:commons-compress from 1.26.0 to 1.26.1.

---
updated-dependencies:
- dependency-name: org.apache.commons:commons-compress
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-23 13:06:46 +02:00
dependabot[bot] a543af9562
Bump i18next from 23.10.0 to 23.10.1 in /Plan/react/dashboard (#3518)
Bumps [i18next](https://github.com/i18next/i18next) from 23.10.0 to 23.10.1.
- [Release notes](https://github.com/i18next/i18next/releases)
- [Changelog](https://github.com/i18next/i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/i18next/compare/v23.10.0...v23.10.1)

---
updated-dependencies:
- dependency-name: i18next
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-23 13:06:31 +02:00
dependabot[bot] 9a605994e8
Bump axios from 1.6.7 to 1.6.8 in /Plan/react/dashboard (#3530)
Bumps [axios](https://github.com/axios/axios) from 1.6.7 to 1.6.8.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.6.7...v1.6.8)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-23 13:05:43 +02:00
dependabot[bot] aac41547db
Bump swagger-ui from 5.11.10 to 5.12.0 in /Plan/react/dashboard (#3517)
Bumps [swagger-ui](https://github.com/swagger-api/swagger-ui) from 5.11.10 to 5.12.0.
- [Release notes](https://github.com/swagger-api/swagger-ui/releases)
- [Changelog](https://github.com/swagger-api/swagger-ui/blob/master/.releaserc)
- [Commits](https://github.com/swagger-api/swagger-ui/compare/v5.11.10...v5.12.0)

---
updated-dependencies:
- dependency-name: swagger-ui
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-23 13:04:19 +02:00
Aurora Lahtela 6d9494d680
Add mcmdev as contributor
For this contribution https://github.com/plan-player-analytics/Extension-LibertyBans/pull/9
2024-03-21 21:28:17 +02:00
Aurora Lahtela b800a9b3ee Bump Extension-LibertyBans to 1.1.0-R1.5 2024-03-20 17:13:05 +02:00
jhqwqmc bd8d92b45c
Update locale_CN.yml (#3516) 2024-03-17 20:15:14 +02:00
Aurora Lahtela 4fb67d7ba7 Update French Locale (FR) by Sniper_TVmc 2024-03-14 20:44:50 +02:00
Aurora Lahtela a4cd8257e2 Fix New Query view not opening at all
The Edit Query effect broke when view wasn't present somewhere
2024-03-14 20:43:23 +02:00
Aurora Lahtela 20b8ab9baa Add 'Edit Query' button to Query Results
This allows easier editing of existing query results
2024-03-14 20:17:08 +02:00
AuroraLS3 e021162729 Update versions.txt 5.6 build 2820 2024-03-14 15:16:46 +00:00
Aurora Lahtela 7463d4e440 Fix exception related to CONCAT on SQLite in Extension boolean storage
Affects issues:
- Fixed #3514
2024-03-11 20:52:41 +02:00
Aurora Lahtela 9fa1a94301 Fix join address not appearing in /plan ingame
Affects issues:
- Fixed #3513
2024-03-11 20:30:31 +02:00
jhqwqmc 30532acf46
Update locale_CN.yml (#3512) 2024-03-11 20:18:30 +02:00
AuroraLS3 4617876e44 Update versions.txt 5.6 build 2816 2024-03-10 08:46:34 +00:00
Aurora Lahtela 252832fcf6 Bump Extension-FastLogin to R1.3
Increase wait up to 9 seconds, until not unknown anymore

Affects issues:
- Close #3485
2024-03-10 10:27:13 +02:00
Aurora Lahtela 8116063e62
Whitelist bounce gathering (#3511)
* Store bounced whitelist logins

* Add allowlist bounce endpoint

* Restore locale file indent from master branch

* Add UI for allowlist

* Update locale

* Fix sonar detected bug and implement database tests

Affects issues:
- Close #2233
2024-03-10 10:25:42 +02:00
Aurora Lahtela 24e6af2d03 Bump Extension-Quests to 5.0.1-R1.0
Affects issues:
- Fixed #3347
- Close https://github.com/PikaMug/Quests/issues/2192
2024-03-10 10:22:23 +02:00
Aurora Lahtela 7299e10064 Fix InstalledPluginGatheringTask running on server thread 2024-03-09 21:15:43 +02:00
Aurora Lahtela de9f9ec5b4
Sonar fixes (#3510)
* Remove deprecated code

- RemoveUnsatisfiedConditionalPlayerResultsTransaction.java
- RemoveUnsatisfiedConditionalServerResultsTransaction.java

* Fix apache compress deprecations

- Use org.apache.commons.io.IOUtils instead of org.apache.commons.compress.utils.IOUtils
- Use TarArchiveInputStream#getNextEntry instead of getNextTarEntry

* Rename variable in BukkitPingCounter

* Extract ApiServices from PlanSystem
2024-03-09 14:43:41 +02:00
Aurora Lahtela 670ef2aff3 Fix exception when storing Extension boolean values with MySQL
Query was using reserved keyword 'stored' as a table alias

Changed it to use 'indb' as the alias

Affects issues:
- Fixed #3508
2024-03-09 10:51:53 +02:00
Aurora Lahtela 9c43287f60 Add Join Address to /plan ingame and placeholders
Adds a %plan_player_join_address% placeholder

Affects issues:
- Close #3463
2024-03-09 10:44:33 +02:00
Aurora Lahtela 9ade3fbf01 Skip yarn build steps on Jitpack
Jitpack doesn't have gclib required for Node 20 which means all builds fail there when yarn build is attempted.

This makes yarn tasks conditional dependency with -PisJitpack flag which skips the problematic section

Affects issues:
- Possibly fixed #3411
2024-03-09 10:29:44 +02:00
Aurora Lahtela 3aa8a71501 Bump Extension-FastLogin to R1.2
Wait one second before asking premium status

Affects issues:
- Possibly fixed #3485
2024-03-09 10:21:14 +02:00
dependabot[bot] 9e29b5aa6b
Bump mockitoVersion from 5.10.0 to 5.11.0 in /Plan (#3505)
Bumps `mockitoVersion` from 5.10.0 to 5.11.0.

Updates `org.mockito:mockito-core` from 5.10.0 to 5.11.0
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v5.10.0...v5.11.0)

Updates `org.mockito:mockito-junit-jupiter` from 5.10.0 to 5.11.0
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v5.10.0...v5.11.0)

---
updated-dependencies:
- dependency-name: org.mockito:mockito-core
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: org.mockito:mockito-junit-jupiter
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-09 10:12:03 +02:00
dependabot[bot] 94c13f38e4
Bump export-to-csv from 1.2.3 to 1.2.4 in /Plan/react/dashboard (#3503)
Bumps [export-to-csv](https://github.com/alexcaza/export-to-csv) from 1.2.3 to 1.2.4.
- [Release notes](https://github.com/alexcaza/export-to-csv/releases)
- [Commits](https://github.com/alexcaza/export-to-csv/compare/v1.2.3...v1.2.4)

---
updated-dependencies:
- dependency-name: export-to-csv
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-09 10:11:52 +02:00
dependabot[bot] b244d43635
Bump react-router-dom from 6.22.2 to 6.22.3 in /Plan/react/dashboard (#3500)
Bumps [react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom) from 6.22.2 to 6.22.3.
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router-dom/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router-dom@6.22.3/packages/react-router-dom)

---
updated-dependencies:
- dependency-name: react-router-dom
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-09 10:11:37 +02:00
dependabot[bot] 05a61ec75a
Bump swagger-ui from 5.11.8 to 5.11.10 in /Plan/react/dashboard (#3501)
Bumps [swagger-ui](https://github.com/swagger-api/swagger-ui) from 5.11.8 to 5.11.10.
- [Release notes](https://github.com/swagger-api/swagger-ui/releases)
- [Changelog](https://github.com/swagger-api/swagger-ui/blob/master/.releaserc)
- [Commits](https://github.com/swagger-api/swagger-ui/compare/v5.11.8...v5.11.10)

---
updated-dependencies:
- dependency-name: swagger-ui
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-09 10:11:30 +02:00
dependabot[bot] 51d31daa81
Bump react-i18next from 14.0.5 to 14.1.0 in /Plan/react/dashboard (#3502)
Bumps [react-i18next](https://github.com/i18next/react-i18next) from 14.0.5 to 14.1.0.
- [Changelog](https://github.com/i18next/react-i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/react-i18next/compare/v14.0.5...v14.1.0)

---
updated-dependencies:
- dependency-name: react-i18next
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-09 10:11:23 +02:00
dependabot[bot] 0c2b4e1b3e
Bump com.github.seancfoley:ipaddress from 5.4.2 to 5.5.0 in /Plan (#3507)
Bumps [com.github.seancfoley:ipaddress](https://github.com/seancfoley/IPAddress) from 5.4.2 to 5.5.0.
- [Release notes](https://github.com/seancfoley/IPAddress/releases)
- [Commits](https://github.com/seancfoley/IPAddress/compare/v5.4.2...v5.5.0)

---
updated-dependencies:
- dependency-name: com.github.seancfoley:ipaddress
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-09 10:11:16 +02:00
dependabot[bot] 520adf6ca8
Bump testContainersVersion from 1.19.6 to 1.19.7 in /Plan (#3506)
Bumps `testContainersVersion` from 1.19.6 to 1.19.7.

Updates `org.testcontainers:testcontainers` from 1.19.6 to 1.19.7
- [Release notes](https://github.com/testcontainers/testcontainers-java/releases)
- [Changelog](https://github.com/testcontainers/testcontainers-java/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testcontainers/testcontainers-java/compare/1.19.6...1.19.7)

Updates `org.testcontainers:junit-jupiter` from 1.19.6 to 1.19.7
- [Release notes](https://github.com/testcontainers/testcontainers-java/releases)
- [Changelog](https://github.com/testcontainers/testcontainers-java/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testcontainers/testcontainers-java/compare/1.19.6...1.19.7)

Updates `org.testcontainers:nginx` from 1.19.6 to 1.19.7
- [Release notes](https://github.com/testcontainers/testcontainers-java/releases)
- [Changelog](https://github.com/testcontainers/testcontainers-java/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testcontainers/testcontainers-java/compare/1.19.6...1.19.7)

---
updated-dependencies:
- dependency-name: org.testcontainers:testcontainers
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.testcontainers:junit-jupiter
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.testcontainers:nginx
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-09 10:11:02 +02:00
dependabot[bot] 1a0f0f33a7
Bump vite from 5.1.4 to 5.1.5 in /Plan/react/dashboard (#3504)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.1.4 to 5.1.5.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.1.5/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-09 10:10:54 +02:00
Aurora Lahtela 1beb1ec4bd Revert SQLite driver back to 3.42.0.1
Affects issues:
- #3436
2024-03-09 10:10:01 +02:00
Aurora Lahtela 70e3f394ba Change player ping graph data format
HighCharts error 12 was occurring due to too many data points

This commit changes player ping graph data to be served in format expected by turbo-mode so that it renders.

Affects issues:
- Fixed #3498
2024-03-05 20:10:20 +02:00
AuroraLS3 e1b4e34e77 Update versions.txt 5.6 DEV build 2796 2024-03-03 08:39:26 +00:00
dependabot[bot] a9ab23347a
Bump @fullcalendar/react from 6.1.10 to 6.1.11 in /Plan/react/dashboard (#3495)
Bumps [@fullcalendar/react](https://github.com/fullcalendar/fullcalendar-react) from 6.1.10 to 6.1.11.
- [Release notes](https://github.com/fullcalendar/fullcalendar-react/releases)
- [Changelog](https://github.com/fullcalendar/fullcalendar-react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/fullcalendar/fullcalendar-react/compare/v6.1.10...v6.1.11)

---
updated-dependencies:
- dependency-name: "@fullcalendar/react"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-03 10:21:23 +02:00
dependabot[bot] c764b2cb31
Bump @fullcalendar/daygrid in /Plan/react/dashboard (#3496)
Bumps [@fullcalendar/daygrid](https://github.com/fullcalendar/fullcalendar/tree/HEAD/packages/daygrid) from 6.1.10 to 6.1.11.
- [Release notes](https://github.com/fullcalendar/fullcalendar/releases)
- [Changelog](https://github.com/fullcalendar/fullcalendar/blob/main/CHANGELOG.md)
- [Commits](https://github.com/fullcalendar/fullcalendar/commits/v6.1.11/packages/daygrid)

---
updated-dependencies:
- dependency-name: "@fullcalendar/daygrid"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-03 10:21:07 +02:00
dependabot[bot] e10185f344
Bump @fullcalendar/bootstrap in /Plan/react/dashboard (#3491)
Bumps [@fullcalendar/bootstrap](https://github.com/fullcalendar/fullcalendar/tree/HEAD/packages/bootstrap4) from 6.1.10 to 6.1.11.
- [Release notes](https://github.com/fullcalendar/fullcalendar/releases)
- [Changelog](https://github.com/fullcalendar/fullcalendar/blob/main/CHANGELOG.md)
- [Commits](https://github.com/fullcalendar/fullcalendar/commits/v6.1.11/packages/bootstrap4)

---
updated-dependencies:
- dependency-name: "@fullcalendar/bootstrap"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-03 10:19:47 +02:00
dependabot[bot] bdbd403e3b
Bump daggerVersion from 2.50 to 2.51 in /Plan (#3490)
Bumps `daggerVersion` from 2.50 to 2.51.

Updates `com.google.dagger:dagger` from 2.50 to 2.51
- [Release notes](https://github.com/google/dagger/releases)
- [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/dagger/compare/dagger-2.50...dagger-2.51)

Updates `com.google.dagger:dagger-compiler` from 2.50 to 2.51
- [Release notes](https://github.com/google/dagger/releases)
- [Changelog](https://github.com/google/dagger/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/dagger/compare/dagger-2.50...dagger-2.51)

---
updated-dependencies:
- dependency-name: com.google.dagger:dagger
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: com.google.dagger:dagger-compiler
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-03 10:19:38 +02:00
dependabot[bot] 2c7813e795
Bump react-router-dom from 6.22.1 to 6.22.2 in /Plan/react/dashboard (#3492)
Bumps [react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom) from 6.22.1 to 6.22.2.
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router-dom/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router-dom@6.22.2/packages/react-router-dom)

---
updated-dependencies:
- dependency-name: react-router-dom
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-03 10:19:08 +02:00
dependabot[bot] 0467c0ddd1
Bump swagger-ui from 5.11.7 to 5.11.8 in /Plan/react/dashboard (#3493)
Bumps [swagger-ui](https://github.com/swagger-api/swagger-ui) from 5.11.7 to 5.11.8.
- [Release notes](https://github.com/swagger-api/swagger-ui/releases)
- [Changelog](https://github.com/swagger-api/swagger-ui/blob/master/.releaserc)
- [Commits](https://github.com/swagger-api/swagger-ui/compare/v5.11.7...v5.11.8)

---
updated-dependencies:
- dependency-name: swagger-ui
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-03 10:18:57 +02:00
dependabot[bot] e33033808c
Bump @fullcalendar/interaction in /Plan/react/dashboard (#3494)
Bumps [@fullcalendar/interaction](https://github.com/fullcalendar/fullcalendar/tree/HEAD/packages/interaction) from 6.1.10 to 6.1.11.
- [Release notes](https://github.com/fullcalendar/fullcalendar/releases)
- [Changelog](https://github.com/fullcalendar/fullcalendar/blob/main/CHANGELOG.md)
- [Commits](https://github.com/fullcalendar/fullcalendar/commits/v6.1.11/packages/interaction)

---
updated-dependencies:
- dependency-name: "@fullcalendar/interaction"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-03 10:18:33 +02:00
Aurora Lahtela 5044b6e365 Fix server/network calendar being editable 2024-03-03 10:18:36 +02:00
Aurora Lahtela 12cf9ea414 Fix Today/Yesterday wrong with negative timezones
Applied offset to "now" when comparing with offset timestamps.

Affects issues:
- Possibly fixed #3420
2024-03-03 10:16:02 +02:00
Aurora Lahtela 701866cc6a Don't try to delete conditional providers if there's nothing to delete 2024-03-02 09:14:27 +02:00
Aurora Lahtela 7368eccbbd Optimize unsatisfied extension conditional value cleanup
Extensions support @Conditional value where a boolean provider determines if other values should exist.
Unsatisfied values were being removed during database cleanup task.
The cleanup transaction was very slow and could hang the server if it was performed near shutdown.

The cleanup is now performed on boolean value change (individual value for one player)
instead of with large cleanup transaction (all values and all players).

Affects issues:
- #3436
2024-03-02 08:53:38 +02:00
dependabot[bot] 3ddfe6b166
Bump org.mariadb.jdbc:mariadb-java-client from 3.3.2 to 3.3.3 in /Plan (#3484)
Bumps [org.mariadb.jdbc:mariadb-java-client](https://github.com/mariadb-corporation/mariadb-connector-j) from 3.3.2 to 3.3.3.
- [Release notes](https://github.com/mariadb-corporation/mariadb-connector-j/releases)
- [Changelog](https://github.com/mariadb-corporation/mariadb-connector-j/blob/master/CHANGELOG.md)
- [Commits](https://github.com/mariadb-corporation/mariadb-connector-j/compare/3.3.2...3.3.3)

---
updated-dependencies:
- dependency-name: org.mariadb.jdbc:mariadb-java-client
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-28 20:21:44 +02:00
dependabot[bot] 68140ba7f9
Bump org.apache.commons:commons-compress from 1.25.0 to 1.26.0 in /Plan (#3473)
Bumps org.apache.commons:commons-compress from 1.25.0 to 1.26.0.

---
updated-dependencies:
- dependency-name: org.apache.commons:commons-compress
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-28 20:21:28 +02:00
dependabot[bot] b03a4be38b
Bump react-router-dom from 6.22.0 to 6.22.1 in /Plan/react/dashboard (#3476)
Bumps [react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom) from 6.22.0 to 6.22.1.
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/react-router-dom@6.22.1/packages/react-router-dom/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router-dom@6.22.1/packages/react-router-dom)

---
updated-dependencies:
- dependency-name: react-router-dom
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-28 20:21:19 +02:00
dependabot[bot] d4aa1e254d
Bump export-to-csv from 1.2.2 to 1.2.3 in /Plan/react/dashboard (#3480)
Bumps [export-to-csv](https://github.com/alexcaza/export-to-csv) from 1.2.2 to 1.2.3.
- [Release notes](https://github.com/alexcaza/export-to-csv/releases)
- [Commits](https://github.com/alexcaza/export-to-csv/compare/v1.2.2...v1.2.3)

---
updated-dependencies:
- dependency-name: export-to-csv
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-28 20:21:13 +02:00
dependabot[bot] 7fd7f704bf
Bump i18next from 23.8.2 to 23.10.0 in /Plan/react/dashboard (#3477)
Bumps [i18next](https://github.com/i18next/i18next) from 23.8.2 to 23.10.0.
- [Release notes](https://github.com/i18next/i18next/releases)
- [Changelog](https://github.com/i18next/i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/i18next/compare/v23.8.2...v23.10.0)

---
updated-dependencies:
- dependency-name: i18next
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-28 20:20:56 +02:00
dependabot[bot] 2cfd0bff2f
Bump testContainersVersion from 1.19.5 to 1.19.6 in /Plan (#3474)
Bumps `testContainersVersion` from 1.19.5 to 1.19.6.

Updates `org.testcontainers:testcontainers` from 1.19.5 to 1.19.6
- [Release notes](https://github.com/testcontainers/testcontainers-java/releases)
- [Changelog](https://github.com/testcontainers/testcontainers-java/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testcontainers/testcontainers-java/compare/1.19.5...1.19.6)

Updates `org.testcontainers:junit-jupiter` from 1.19.5 to 1.19.6
- [Release notes](https://github.com/testcontainers/testcontainers-java/releases)
- [Changelog](https://github.com/testcontainers/testcontainers-java/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testcontainers/testcontainers-java/compare/1.19.5...1.19.6)

Updates `org.testcontainers:nginx` from 1.19.5 to 1.19.6
- [Release notes](https://github.com/testcontainers/testcontainers-java/releases)
- [Changelog](https://github.com/testcontainers/testcontainers-java/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testcontainers/testcontainers-java/compare/1.19.5...1.19.6)

---
updated-dependencies:
- dependency-name: org.testcontainers:testcontainers
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.testcontainers:junit-jupiter
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.testcontainers:nginx
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-28 19:40:22 +02:00
dependabot[bot] f2749a33ff
Bump sass from 1.70.0 to 1.71.1 in /Plan/react/dashboard (#3478)
Bumps [sass](https://github.com/sass/dart-sass) from 1.70.0 to 1.71.1.
- [Release notes](https://github.com/sass/dart-sass/releases)
- [Changelog](https://github.com/sass/dart-sass/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sass/dart-sass/compare/1.70.0...1.71.1)

---
updated-dependencies:
- dependency-name: sass
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-28 19:40:05 +02:00
dependabot[bot] f69a2858ee
Bump org.seleniumhq.selenium:selenium-java in /Plan (#3475)
Bumps [org.seleniumhq.selenium:selenium-java](https://github.com/SeleniumHQ/selenium) from 4.17.0 to 4.18.1.
- [Release notes](https://github.com/SeleniumHQ/selenium/releases)
- [Commits](https://github.com/SeleniumHQ/selenium/compare/selenium-4.17.0...selenium-4.18.1)

---
updated-dependencies:
- dependency-name: org.seleniumhq.selenium:selenium-java
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-28 19:39:37 +02:00
dependabot[bot] f78dca306c
Bump vite from 5.1.3 to 5.1.4 in /Plan/react/dashboard (#3479)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.1.3 to 5.1.4.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.1.4/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-28 19:39:12 +02:00
dependabot[bot] 8861e99abc
Bump @fullcalendar/core from 6.1.10 to 6.1.11 in /Plan/react/dashboard (#3481)
Bumps [@fullcalendar/core](https://github.com/fullcalendar/fullcalendar/tree/HEAD/packages/core) from 6.1.10 to 6.1.11.
- [Release notes](https://github.com/fullcalendar/fullcalendar/releases)
- [Changelog](https://github.com/fullcalendar/fullcalendar/blob/main/CHANGELOG.md)
- [Commits](https://github.com/fullcalendar/fullcalendar/commits/v6.1.11/packages/core)

---
updated-dependencies:
- dependency-name: "@fullcalendar/core"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-28 19:38:47 +02:00
dependabot[bot] 34013901c7
Bump i18next-http-backend from 2.4.3 to 2.5.0 in /Plan/react/dashboard (#3482)
Bumps [i18next-http-backend](https://github.com/i18next/i18next-http-backend) from 2.4.3 to 2.5.0.
- [Changelog](https://github.com/i18next/i18next-http-backend/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/i18next-http-backend/compare/v2.4.3...v2.5.0)

---
updated-dependencies:
- dependency-name: i18next-http-backend
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-28 19:38:16 +02:00
dependabot[bot] a2147f2fab
Bump bootstrap from 5.3.2 to 5.3.3 in /Plan/react/dashboard (#3483)
Bumps [bootstrap](https://github.com/twbs/bootstrap) from 5.3.2 to 5.3.3.
- [Release notes](https://github.com/twbs/bootstrap/releases)
- [Commits](https://github.com/twbs/bootstrap/compare/v5.3.2...v5.3.3)

---
updated-dependencies:
- dependency-name: bootstrap
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-28 19:38:06 +02:00
Aurora Lahtela 367d8af59a
Update action versions 2024-02-28 17:23:47 +02:00
Aurora Lahtela 6ed23f0c0b Download GeoLite2 Country database from playeranalytics.net
Affects issues:
- Fixed #3452
2024-02-28 11:47:16 +02:00
Aurora Lahtela 4042980379 Fix build issue 2024-02-28 10:10:35 +02:00
Aurora Lahtela 092533d0b7 Change order of guards to allow IP blocklisted requests to be rate-limited 2024-02-28 10:09:21 +02:00
Aurora Lahtela ab94ab9125 Rate limit against simple DDoS
Affects issues:
- Close #1846
- Close #3486
2024-02-28 10:02:26 +02:00
Aurora Lahtela b50fa10e12 Reduce DDoS impact of access log transactions
Made Plan skip access logging if transaction queue is larger than 500 transactions
Reduced amount of objects held by access log transaction by serializing request properties to objects before passing to transaction.

Affects issues:
- #3486
2024-02-27 10:11:45 +02:00
dependabot[bot] a3a2085c8a
Bump vite from 5.0.12 to 5.1.3 in /Plan/react/dashboard (#3465)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.0.12 to 5.1.3.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.1.3/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-17 10:32:20 +02:00
dependabot[bot] 8170f4f974
Bump react-bootstrap from 2.10.0 to 2.10.1 in /Plan/react/dashboard (#3464)
Bumps [react-bootstrap](https://github.com/react-bootstrap/react-bootstrap) from 2.10.0 to 2.10.1.
- [Release notes](https://github.com/react-bootstrap/react-bootstrap/releases)
- [Changelog](https://github.com/react-bootstrap/react-bootstrap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/react-bootstrap/react-bootstrap/compare/v2.10.0...v2.10.1)

---
updated-dependencies:
- dependency-name: react-bootstrap
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-17 10:12:51 +02:00
dependabot[bot] d15cf82e9c
Bump undici from 5.26.3 to 5.28.3 in /Plan/react/dashboard (#3467)
Bumps [undici](https://github.com/nodejs/undici) from 5.26.3 to 5.28.3.
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](https://github.com/nodejs/undici/compare/v5.26.3...v5.28.3)

---
updated-dependencies:
- dependency-name: undici
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-17 10:12:05 +02:00
dependabot[bot] b1566dfe80
Bump swagger-ui from 5.11.2 to 5.11.7 in /Plan/react/dashboard (#3468)
Bumps [swagger-ui](https://github.com/swagger-api/swagger-ui) from 5.11.2 to 5.11.7.
- [Release notes](https://github.com/swagger-api/swagger-ui/releases)
- [Changelog](https://github.com/swagger-api/swagger-ui/blob/master/.releaserc)
- [Commits](https://github.com/swagger-api/swagger-ui/compare/v5.11.2...v5.11.7)

---
updated-dependencies:
- dependency-name: swagger-ui
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-17 10:11:56 +02:00
dependabot[bot] 1297ede72d
Bump @testing-library/jest-dom in /Plan/react/dashboard (#3458)
Bumps [@testing-library/jest-dom](https://github.com/testing-library/jest-dom) from 6.4.1 to 6.4.2.
- [Release notes](https://github.com/testing-library/jest-dom/releases)
- [Changelog](https://github.com/testing-library/jest-dom/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testing-library/jest-dom/compare/v6.4.1...v6.4.2)

---
updated-dependencies:
- dependency-name: "@testing-library/jest-dom"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-17 09:26:02 +02:00
dependabot[bot] f8553009f3
Bump org.junit.jupiter:junit-jupiter from 5.10.1 to 5.10.2 in /Plan (#3454)
Bumps [org.junit.jupiter:junit-jupiter](https://github.com/junit-team/junit5) from 5.10.1 to 5.10.2.
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.10.1...r5.10.2)

---
updated-dependencies:
- dependency-name: org.junit.jupiter:junit-jupiter
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-17 09:25:38 +02:00
dependabot[bot] 7d3ab667d8
Bump testContainersVersion from 1.19.4 to 1.19.5 in /Plan (#3453)
Bumps `testContainersVersion` from 1.19.4 to 1.19.5.

Updates `org.testcontainers:testcontainers` from 1.19.4 to 1.19.5
- [Release notes](https://github.com/testcontainers/testcontainers-java/releases)
- [Changelog](https://github.com/testcontainers/testcontainers-java/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testcontainers/testcontainers-java/compare/1.19.4...1.19.5)

Updates `org.testcontainers:junit-jupiter` from 1.19.4 to 1.19.5
- [Release notes](https://github.com/testcontainers/testcontainers-java/releases)
- [Changelog](https://github.com/testcontainers/testcontainers-java/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testcontainers/testcontainers-java/compare/1.19.4...1.19.5)

Updates `org.testcontainers:nginx` from 1.19.4 to 1.19.5
- [Release notes](https://github.com/testcontainers/testcontainers-java/releases)
- [Changelog](https://github.com/testcontainers/testcontainers-java/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testcontainers/testcontainers-java/compare/1.19.4...1.19.5)

---
updated-dependencies:
- dependency-name: org.testcontainers:testcontainers
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.testcontainers:junit-jupiter
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.testcontainers:nginx
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-17 09:25:24 +02:00
dependabot[bot] a4e2f0d6f7
Bump react-i18next from 14.0.1 to 14.0.5 in /Plan/react/dashboard (#3459)
Bumps [react-i18next](https://github.com/i18next/react-i18next) from 14.0.1 to 14.0.5.
- [Changelog](https://github.com/i18next/react-i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/react-i18next/compare/v14.0.1...v14.0.5)

---
updated-dependencies:
- dependency-name: react-i18next
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-17 09:24:13 +02:00
dependabot[bot] e955f80daa
Bump commons-codec:commons-codec from 1.16.0 to 1.16.1 in /Plan (#3457)
Bumps [commons-codec:commons-codec](https://github.com/apache/commons-codec) from 1.16.0 to 1.16.1.
- [Changelog](https://github.com/apache/commons-codec/blob/master/RELEASE-NOTES.txt)
- [Commits](https://github.com/apache/commons-codec/compare/rel/commons-codec-1.16.0...rel/commons-codec-1.16.1)

---
updated-dependencies:
- dependency-name: commons-codec:commons-codec
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-17 09:24:00 +02:00
dependabot[bot] aad3073c92
Bump com.github.node-gradle.node from 7.0.1 to 7.0.2 in /Plan (#3456)
Bumps com.github.node-gradle.node from 7.0.1 to 7.0.2.

---
updated-dependencies:
- dependency-name: com.github.node-gradle.node
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-17 09:23:25 +02:00
Aurora Lahtela b867bcebdb Don't save sessions on server shutdown if database already closed
Affects issues:
- #3436
2024-02-17 09:19:38 +02:00
Aurora Lahtela bede36957b Fix ShutdownSaveTest 2024-02-17 09:11:37 +02:00
Aurora Lahtela 2daf3943b7 Make call-site for SQLite JVM wait more accurate
Affects issues:
- #3436
2024-02-17 09:06:45 +02:00
Aurora Lahtela e041e193fc Close transaction queue after connection wait on SQLite
Affects issues:
- #3436
2024-02-11 15:47:39 +02:00
Aurora Lahtela bf3bdb599d Log connection waiting sites without dev mode 2024-02-04 10:05:48 +02:00
dependabot[bot] 8ced48d815
Bump @testing-library/jest-dom in /Plan/react/dashboard (#3444)
Bumps [@testing-library/jest-dom](https://github.com/testing-library/jest-dom) from 6.3.0 to 6.4.1.
- [Release notes](https://github.com/testing-library/jest-dom/releases)
- [Changelog](https://github.com/testing-library/jest-dom/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testing-library/jest-dom/compare/v6.3.0...v6.4.1)

---
updated-dependencies:
- dependency-name: "@testing-library/jest-dom"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-03 14:49:19 +02:00
dependabot[bot] 45833d5fc7
Bump com.github.seancfoley:ipaddress from 5.4.0 to 5.4.2 in /Plan (#3451)
Bumps [com.github.seancfoley:ipaddress](https://github.com/seancfoley/IPAddress) from 5.4.0 to 5.4.2.
- [Release notes](https://github.com/seancfoley/IPAddress/releases)
- [Commits](https://github.com/seancfoley/IPAddress/compare/v5.4.0...v5.4.2)

---
updated-dependencies:
- dependency-name: com.github.seancfoley:ipaddress
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-03 14:49:10 +02:00
dependabot[bot] ad8256cf04
Bump swagger-ui from 5.11.0 to 5.11.2 in /Plan/react/dashboard (#3443)
Bumps [swagger-ui](https://github.com/swagger-api/swagger-ui) from 5.11.0 to 5.11.2.
- [Release notes](https://github.com/swagger-api/swagger-ui/releases)
- [Changelog](https://github.com/swagger-api/swagger-ui/blob/master/.releaserc)
- [Commits](https://github.com/swagger-api/swagger-ui/compare/v5.11.0...v5.11.2)

---
updated-dependencies:
- dependency-name: swagger-ui
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-03 13:53:58 +02:00
dependabot[bot] 0ccafd9050
Bump react-router-dom from 6.21.3 to 6.22.0 in /Plan/react/dashboard (#3447)
Bumps [react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom) from 6.21.3 to 6.22.0.
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router-dom/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router-dom@6.22.0/packages/react-router-dom)

---
updated-dependencies:
- dependency-name: react-router-dom
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-03 13:53:21 +02:00
dependabot[bot] 60f2aa6c42
Bump i18next-http-backend from 2.4.2 to 2.4.3 in /Plan/react/dashboard (#3446)
Bumps [i18next-http-backend](https://github.com/i18next/i18next-http-backend) from 2.4.2 to 2.4.3.
- [Changelog](https://github.com/i18next/i18next-http-backend/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/i18next-http-backend/compare/v2.4.2...v2.4.3)

---
updated-dependencies:
- dependency-name: i18next-http-backend
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-03 13:53:06 +02:00
dependabot[bot] 468bd32111
Bump i18next from 23.7.19 to 23.8.2 in /Plan/react/dashboard (#3442)
Bumps [i18next](https://github.com/i18next/i18next) from 23.7.19 to 23.8.2.
- [Release notes](https://github.com/i18next/i18next/releases)
- [Changelog](https://github.com/i18next/i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/i18next/compare/v23.7.19...v23.8.2)

---
updated-dependencies:
- dependency-name: i18next
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-03 13:52:45 +02:00
dependabot[bot] ed543e460e
Bump @testing-library/react in /Plan/react/dashboard (#3445)
Bumps [@testing-library/react](https://github.com/testing-library/react-testing-library) from 14.1.2 to 14.2.1.
- [Release notes](https://github.com/testing-library/react-testing-library/releases)
- [Changelog](https://github.com/testing-library/react-testing-library/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testing-library/react-testing-library/compare/v14.1.2...v14.2.1)

---
updated-dependencies:
- dependency-name: "@testing-library/react"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-03 13:52:35 +02:00
dependabot[bot] cac627a2f7
Bump jettyVersion from 11.0.19 to 11.0.20 in /Plan (#3450)
Bumps `jettyVersion` from 11.0.19 to 11.0.20.

Updates `org.eclipse.jetty:jetty-server` from 11.0.19 to 11.0.20

Updates `org.eclipse.jetty:jetty-alpn-java-server` from 11.0.19 to 11.0.20

Updates `org.eclipse.jetty.http2:http2-server` from 11.0.19 to 11.0.20

---
updated-dependencies:
- dependency-name: org.eclipse.jetty:jetty-server
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.eclipse.jetty:jetty-alpn-java-server
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.eclipse.jetty.http2:http2-server
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-03 13:52:07 +02:00
dependabot[bot] a0a430507e
Bump org.xerial:sqlite-jdbc from 3.45.0.0 to 3.45.1.0 in /Plan (#3448)
Bumps [org.xerial:sqlite-jdbc](https://github.com/xerial/sqlite-jdbc) from 3.45.0.0 to 3.45.1.0.
- [Release notes](https://github.com/xerial/sqlite-jdbc/releases)
- [Changelog](https://github.com/xerial/sqlite-jdbc/blob/master/CHANGELOG)
- [Commits](https://github.com/xerial/sqlite-jdbc/compare/3.45.0.0...3.45.1.0)

---
updated-dependencies:
- dependency-name: org.xerial:sqlite-jdbc
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-03 13:51:55 +02:00
Aurora Lahtela 5ddbd52d37 Log connection holding call sites in dev-mode 2024-02-03 10:17:31 +02:00
Aurora Lahtela 4615c6b6b0 Optimize unsatisfied condition removal for players with user_id join
The query was joining based on UUID which can be slow since it's a string.
2024-02-03 09:13:00 +02:00
Aurora Lahtela 47d74eee8c Add slf4j-nop:1.7.36 to SQLite driver dependencies
SQLite driver 3.43.2.1 downgraded to slf4j 1.7

Adding the nop library to be loaded by the dependency downloader
will stop the error message since slf4j-nop 1.7 is loaded.

Affects issues:
- Fixed #3435
2024-01-28 12:34:37 +02:00
甜力怕 bda96726f8
Update locale_CN.yml by liuzhen932 (#3437) 2024-01-28 09:05:07 +02:00
Aurora Lahtela 2b93919b5e Use better loader for PlayerTable 2024-01-27 21:04:16 +02:00
Aurora Lahtela a8decff8e8 Make Ping Table use DataTablesTable
Also fixed issue where server ping table never loaded
2024-01-27 21:02:04 +02:00
Aurora Lahtela 67c487b820 Fix Best and Worst ping formatting on player table 2024-01-27 14:21:34 +02:00
Aurora Lahtela ff7e7791f3 Refactor PlayerListCard to also have PlayerTable 2024-01-27 14:13:08 +02:00
Aurora Lahtela 3ad5d577d4 Make Query view affect ping data retrieved
All ping data was being used to create average.

This allows comparing ping over time
2024-01-27 14:01:14 +02:00
Aurora Lahtela 7494902e46 Add regular_players and network_regular_players placeholders
Affects issues:
- Close #3425
2024-01-27 13:38:06 +02:00
Aurora Lahtela 01ce503c77 Add network session placeholders
Affects issues:
- Close #2267
2024-01-27 13:35:03 +02:00
AuroraLS3 465af8e803 Update versions.txt 2024-01-27 08:08:01 +00:00
dependabot[bot] 512defb3f8
Bump org.seleniumhq.selenium:selenium-java from 4.12.1 to 4.17.0 in /Plan (#3434)
* Bump org.seleniumhq.selenium:selenium-java in /Plan

Bumps [org.seleniumhq.selenium:selenium-java](https://github.com/SeleniumHQ/selenium) from 4.12.1 to 4.17.0.
- [Release notes](https://github.com/SeleniumHQ/selenium/releases)
- [Commits](https://github.com/SeleniumHQ/selenium/commits/selenium-4.17.0)

---
updated-dependencies:
- dependency-name: org.seleniumhq.selenium:selenium-java
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* Use new headless setup method

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Aurora Lahtela <24460436+AuroraLS3@users.noreply.github.com>
2024-01-27 09:49:50 +02:00
dependabot[bot] 89355d4975
Bump testContainersVersion from 1.19.3 to 1.19.4 in /Plan (#3431)
Bumps `testContainersVersion` from 1.19.3 to 1.19.4.

Updates `org.testcontainers:testcontainers` from 1.19.3 to 1.19.4
- [Release notes](https://github.com/testcontainers/testcontainers-java/releases)
- [Changelog](https://github.com/testcontainers/testcontainers-java/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testcontainers/testcontainers-java/compare/1.19.3...1.19.4)

Updates `org.testcontainers:junit-jupiter` from 1.19.3 to 1.19.4
- [Release notes](https://github.com/testcontainers/testcontainers-java/releases)
- [Changelog](https://github.com/testcontainers/testcontainers-java/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testcontainers/testcontainers-java/compare/1.19.3...1.19.4)

Updates `org.testcontainers:nginx` from 1.19.3 to 1.19.4
- [Release notes](https://github.com/testcontainers/testcontainers-java/releases)
- [Changelog](https://github.com/testcontainers/testcontainers-java/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testcontainers/testcontainers-java/compare/1.19.3...1.19.4)

---
updated-dependencies:
- dependency-name: org.testcontainers:testcontainers
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.testcontainers:junit-jupiter
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.testcontainers:nginx
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-27 09:26:00 +02:00
dependabot[bot] 4a6987710f
Bump org.xerial:sqlite-jdbc from 3.42.0.1 to 3.45.0.0 in /Plan (#3430)
Bumps [org.xerial:sqlite-jdbc](https://github.com/xerial/sqlite-jdbc) from 3.42.0.1 to 3.45.0.0.
- [Release notes](https://github.com/xerial/sqlite-jdbc/releases)
- [Changelog](https://github.com/xerial/sqlite-jdbc/blob/master/CHANGELOG)
- [Commits](https://github.com/xerial/sqlite-jdbc/compare/3.42.0.1...3.45.0.0)

---
updated-dependencies:
- dependency-name: org.xerial:sqlite-jdbc
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-27 09:25:52 +02:00
dependabot[bot] 79ba13b6fc
Bump react-i18next from 14.0.0 to 14.0.1 in /Plan/react/dashboard (#3427)
Bumps [react-i18next](https://github.com/i18next/react-i18next) from 14.0.0 to 14.0.1.
- [Changelog](https://github.com/i18next/react-i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/react-i18next/compare/v14.0.0...v14.0.1)

---
updated-dependencies:
- dependency-name: react-i18next
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-27 09:14:23 +02:00
dependabot[bot] 7bbc18934a
Bump i18next from 23.7.16 to 23.7.19 in /Plan/react/dashboard (#3428)
Bumps [i18next](https://github.com/i18next/i18next) from 23.7.16 to 23.7.19.
- [Release notes](https://github.com/i18next/i18next/releases)
- [Changelog](https://github.com/i18next/i18next/blob/master/CHANGELOG.md)
- [Commits](https://github.com/i18next/i18next/compare/v23.7.16...v23.7.19)

---
updated-dependencies:
- dependency-name: i18next
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-27 09:14:09 +02:00
dependabot[bot] 66844d4c56
Bump @testing-library/jest-dom in /Plan/react/dashboard (#3429)
Bumps [@testing-library/jest-dom](https://github.com/testing-library/jest-dom) from 6.2.0 to 6.3.0.
- [Release notes](https://github.com/testing-library/jest-dom/releases)
- [Changelog](https://github.com/testing-library/jest-dom/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testing-library/jest-dom/compare/v6.2.0...v6.3.0)

---
updated-dependencies:
- dependency-name: "@testing-library/jest-dom"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-27 09:13:47 +02:00
dependabot[bot] d68bf9fca9
Bump axios from 1.6.5 to 1.6.7 in /Plan/react/dashboard (#3426)
Bumps [axios](https://github.com/axios/axios) from 1.6.5 to 1.6.7.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.6.5...v1.6.7)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-27 09:13:22 +02:00
dependabot[bot] 6584ca0cb7
Bump mockitoVersion from 5.9.0 to 5.10.0 in /Plan (#3433)
Bumps `mockitoVersion` from 5.9.0 to 5.10.0.

Updates `org.mockito:mockito-core` from 5.9.0 to 5.10.0
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v5.9.0...v5.10.0)

Updates `org.mockito:mockito-junit-jupiter` from 5.9.0 to 5.10.0
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v5.9.0...v5.10.0)

---
updated-dependencies:
- dependency-name: org.mockito:mockito-core
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: org.mockito:mockito-junit-jupiter
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-27 09:12:53 +02:00
Aurora Lahtela 8356a0d52e Test and fix top_ placeholders
Affects issues:
- Fixed #3369
2024-01-21 20:08:46 +02:00
Aurora Lahtela c8d0cc91b6 Fixed react bundle getting exported when export is disabled
Caused by missing checks before react export.

Affects issues:
- #3384
2024-01-21 19:33:24 +02:00
Aurora Lahtela ae85f39871 Stop JSON cache mismatch when UUID is missing
The check for {identifier}- meant that lookup for "PLAYERS_ONLINE-"
would also match "PLAYERS_ONLINE-{uuid}-" since start is the same

Fixed this by changing identifiers to PLAYERS_ONLINE_{uuid}-{timestamp}

Affects issues:
- Fixed #3404
2024-01-21 19:21:41 +02:00
Aurora Lahtela 1fdd3289a6 Move fabric command registration earlier
This prevents situation where Plan disables due to an error but reload command is not available

Affects issues:
- Fixed #3392
2024-01-21 13:44:47 +02:00
Aurora Lahtela 9e08794ddd Update locale files 2024-01-21 10:04:17 +02:00
Aurora Lahtela 34a731b70a Add CSV export to all DataTables
Affects issues:
- Close #3413
2024-01-21 10:02:39 +02:00
Aurora Lahtela 673cb4cfdb Remove console log 2024-01-20 11:34:39 +02:00
Aurora Lahtela 8e94d26ff3 Format Server/Network Overview values in the frontend 2024-01-20 11:34:20 +02:00
dependabot[bot] f9d2b0767f
Bump react-router-dom from 6.21.2 to 6.21.3 in /Plan/react/dashboard (#3414)
Bumps [react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom) from 6.21.2 to 6.21.3.
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router-dom/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router-dom@6.21.3/packages/react-router-dom)

---
updated-dependencies:
- dependency-name: react-router-dom
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-20 09:12:49 +02:00
dependabot[bot] 7dfb933295
Bump sass from 1.69.7 to 1.70.0 in /Plan/react/dashboard (#3415)
Bumps [sass](https://github.com/sass/dart-sass) from 1.69.7 to 1.70.0.
- [Release notes](https://github.com/sass/dart-sass/releases)
- [Changelog](https://github.com/sass/dart-sass/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sass/dart-sass/compare/1.69.7...1.70.0)

---
updated-dependencies:
- dependency-name: sass
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-20 09:12:33 +02:00
dependabot[bot] 8e0b2d2734
Bump react-bootstrap from 2.9.2 to 2.10.0 in /Plan/react/dashboard (#3416)
Bumps [react-bootstrap](https://github.com/react-bootstrap/react-bootstrap) from 2.9.2 to 2.10.0.
- [Release notes](https://github.com/react-bootstrap/react-bootstrap/releases)
- [Changelog](https://github.com/react-bootstrap/react-bootstrap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/react-bootstrap/react-bootstrap/compare/v2.9.2...v2.10.0)

---
updated-dependencies:
- dependency-name: react-bootstrap
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-20 09:12:14 +02:00
dependabot[bot] 3717eacad1
Bump mockitoVersion from 5.8.0 to 5.9.0 in /Plan (#3417)
Bumps `mockitoVersion` from 5.8.0 to 5.9.0.

Updates `org.mockito:mockito-core` from 5.8.0 to 5.9.0
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v5.8.0...v5.9.0)

Updates `org.mockito:mockito-junit-jupiter` from 5.8.0 to 5.9.0
- [Release notes](https://github.com/mockito/mockito/releases)
- [Commits](https://github.com/mockito/mockito/compare/v5.8.0...v5.9.0)

---
updated-dependencies:
- dependency-name: org.mockito:mockito-core
  dependency-type: direct:production
  update-type: version-update:semver-minor
- dependency-name: org.mockito:mockito-junit-jupiter
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-20 09:11:57 +02:00
dependabot[bot] 6f179a133b
Bump com.mysql:mysql-connector-j from 8.2.0 to 8.3.0 in /Plan (#3418)
Bumps [com.mysql:mysql-connector-j](https://github.com/mysql/mysql-connector-j) from 8.2.0 to 8.3.0.
- [Changelog](https://github.com/mysql/mysql-connector-j/blob/release/8.x/CHANGES)
- [Commits](https://github.com/mysql/mysql-connector-j/compare/8.2.0...8.3.0)

---
updated-dependencies:
- dependency-name: com.mysql:mysql-connector-j
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-20 09:11:47 +02:00
dependabot[bot] 0ec5552d90
Bump vite from 5.0.11 to 5.0.12 in /Plan/react/dashboard (#3419)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.0.11 to 5.0.12.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v5.0.12/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.0.12/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-20 09:11:11 +02:00
dependabot[bot] 2a387bd0dd
Bump swagger-ui from 5.10.5 to 5.11.0 in /Plan/react/dashboard (#3409)
Bumps [swagger-ui](https://github.com/swagger-api/swagger-ui) from 5.10.5 to 5.11.0.
- [Release notes](https://github.com/swagger-api/swagger-ui/releases)
- [Changelog](https://github.com/swagger-api/swagger-ui/blob/master/.releaserc)
- [Commits](https://github.com/swagger-api/swagger-ui/compare/v5.10.5...v5.11.0)

---
updated-dependencies:
- dependency-name: swagger-ui
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-17 11:36:29 +02:00
dependabot[bot] 26b69e604c
Bump react-router-dom from 6.21.1 to 6.21.2 in /Plan/react/dashboard (#3407)
Bumps [react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom) from 6.21.1 to 6.21.2.
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router-dom/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router-dom@6.21.2/packages/react-router-dom)

---
updated-dependencies:
- dependency-name: react-router-dom
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-17 11:36:06 +02:00
dependabot[bot] 3e41c4dccf
Bump slf4jVersion from 2.0.10 to 2.0.11 in /Plan (#3405)
Bumps `slf4jVersion` from 2.0.10 to 2.0.11.

Updates `org.slf4j:slf4j-nop` from 2.0.10 to 2.0.11

Updates `org.slf4j:slf4j-api` from 2.0.10 to 2.0.11

---
updated-dependencies:
- dependency-name: org.slf4j:slf4j-nop
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: org.slf4j:slf4j-api
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-17 11:35:12 +02:00
dependabot[bot] ae98bcfd69
Bump axios from 1.6.4 to 1.6.5 in /Plan/react/dashboard (#3406)
Bumps [axios](https://github.com/axios/axios) from 1.6.4 to 1.6.5.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.6.4...v1.6.5)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-17 11:35:04 +02:00
dependabot[bot] 05f07da915
Bump vite from 5.0.10 to 5.0.11 in /Plan/react/dashboard (#3408)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.0.10 to 5.0.11.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.0.11/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-17 11:34:51 +02:00
AuroraLS3 d4ab5a53f8 Update versions.txt 5.6 DEV build 2703 2024-01-07 10:10:44 +00:00
203 changed files with 5327 additions and 2830 deletions

View File

@ -23,16 +23,16 @@ jobs:
steps: steps:
- name: 📥 Checkout git repository - name: 📥 Checkout git repository
uses: actions/checkout@v3 uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
- name: ☕ Setup JDK - name: ☕ Setup JDK
uses: actions/setup-java@v3 uses: actions/setup-java@v4
with: with:
distribution: 'adopt' distribution: 'adopt'
java-version: '17' java-version: '21'
- name: 💼 Load Gradle Cache - name: 💼 Load Gradle Cache
uses: actions/cache@v3 uses: actions/cache@v4
with: with:
path: | path: |
~/.gradle/caches ~/.gradle/caches
@ -54,12 +54,12 @@ jobs:
echo "versionString=$(cat build/versions/jar.txt)" >> $GITHUB_ENV echo "versionString=$(cat build/versions/jar.txt)" >> $GITHUB_ENV
echo "artifactPath=$(pwd)/builds" >> $GITHUB_ENV echo "artifactPath=$(pwd)/builds" >> $GITHUB_ENV
- name: 📤 Upload Plan.jar - name: 📤 Upload Plan.jar
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: Plan-${{ env.versionString }}-${{ env.git_hash }}.jar name: Plan-${{ env.versionString }}-${{ env.git_hash }}.jar
path: ${{ env.artifactPath }}/Plan-${{ env.snapshotVersion }}.jar path: ${{ env.artifactPath }}/Plan-${{ env.snapshotVersion }}.jar
- name: 📤 Upload PlanFabric.jar - name: 📤 Upload PlanFabric.jar
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
with: with:
name: PlanFabric-${{ env.versionString }}-${{ env.git_hash }}.jar name: PlanFabric-${{ env.versionString }}-${{ env.git_hash }}.jar
path: ${{ env.artifactPath }}/PlanFabric-${{ env.snapshotVersion }}.jar path: ${{ env.artifactPath }}/PlanFabric-${{ env.snapshotVersion }}.jar

View File

@ -16,8 +16,8 @@ plugins {
id 'java-library' id 'java-library'
id "jacoco" id "jacoco"
id "checkstyle" id "checkstyle"
id "org.sonarqube" version "4.4.1.3373" id "org.sonarqube" version "5.0.0.4638"
id 'fabric-loom' version '1.3-SNAPSHOT' apply false id 'fabric-loom' version '1.6-SNAPSHOT' apply false
} }
apply plugin: 'nebula-aggregate-javadocs' apply plugin: 'nebula-aggregate-javadocs'
@ -69,9 +69,9 @@ subprojects {
} }
ext { ext {
daggerVersion = "2.50" daggerVersion = "2.51.1"
palVersion = "5.1.0" palVersion = "5.2.0"
bukkitVersion = "1.13.2-R0.1-SNAPSHOT" bukkitVersion = "1.13.2-R0.1-SNAPSHOT"
spigotVersion = "1.13.2-R0.1-SNAPSHOT" spigotVersion = "1.13.2-R0.1-SNAPSHOT"
@ -83,32 +83,33 @@ subprojects {
redisBungeeVersion = "0.3.8-SNAPSHOT" redisBungeeVersion = "0.3.8-SNAPSHOT"
redisBungeeProxioDevVersion = "0.7.3" redisBungeeProxioDevVersion = "0.7.3"
commonsTextVersion = "1.11.0" commonsTextVersion = "1.12.0"
commonsCompressVersion = "1.25.0" commonsCompressVersion = "1.26.2"
commonsCodecVersion = "1.16.0" commonsCodecVersion = "1.17.0"
caffeineVersion = "3.1.8" caffeineVersion = "3.1.8"
jettyVersion = "11.0.19" jettyVersion = "11.0.21"
caffeineVersion = "2.9.2" caffeineVersion = "2.9.2"
mysqlVersion = "8.2.0" mysqlVersion = "8.4.0"
mariadbVersion = "3.3.2" mariadbVersion = "3.4.0"
sqliteVersion = "3.42.0.1" sqliteVersion = "3.42.0.1"
adventureVersion = "4.14.0" adventureVersion = "4.17.0"
hikariVersion = "5.1.0" hikariVersion = "5.1.0"
slf4jVersion = "2.0.10" slf4jVersion = "2.0.13"
geoIpVersion = "4.2.0" geoIpVersion = "4.2.0"
gsonVersion = "2.10.1" gsonVersion = "2.11.0"
dependencyDownloadVersion = "1.3.1" dependencyDownloadVersion = "1.3.1"
ipAddressMatcherVersion = "5.4.0" ipAddressMatcherVersion = "5.5.0"
jasyptVersion = "1.9.3" jasyptVersion = "1.9.3"
bstatsVersion = "3.0.2" bstatsVersion = "3.0.2"
placeholderapiVersion = "2.11.5" placeholderapiVersion = "2.11.5"
nkPlaceholderapiVersion = "1.4-SNAPSHOT" nkPlaceholderapiVersion = "1.4-SNAPSHOT"
junitVersion = "5.10.1" junitVersion = "5.10.2"
mockitoVersion = "5.8.0" mockitoVersion = "5.12.0"
testContainersVersion = "1.19.3" seleniumVersion = "4.21.0"
swaggerVersion = "2.2.20" testContainersVersion = "1.19.8"
swaggerVersion = "2.2.22"
} }
repositories { repositories {

View File

@ -36,6 +36,7 @@ import org.bukkit.command.PluginCommand;
import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
import java.lang.reflect.Constructor;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future; import java.util.concurrent.Future;
@ -62,7 +63,18 @@ public class Plan extends JavaPlugin implements PlanPlugin {
@Override @Override
public void onLoad() { public void onLoad() {
abstractionLayer = new BukkitPlatformLayer(this); if (isFolia()) {
try {
// Attempt to load and use the Folia library for Java 17+
Class<?> foliaPlatformLayer = Class.forName("net.playeranalytics.plugin.FoliaPlatformLayer");
abstractionLayer = (PlatformAbstractionLayer) foliaPlatformLayer.getConstructor(JavaPlugin.class).newInstance(this);
} catch (Exception e) {
this.getLogger().log(Level.SEVERE, "Failed to load FoliaPlatformLayer", e);
abstractionLayer = new BukkitPlatformLayer(this);
}
} else {
abstractionLayer = new BukkitPlatformLayer(this);
}
pluginLogger = abstractionLayer.getPluginLogger(); pluginLogger = abstractionLayer.getPluginLogger();
runnableFactory = abstractionLayer.getRunnableFactory(); runnableFactory = abstractionLayer.getRunnableFactory();
} }
@ -167,7 +179,18 @@ public class Plan extends JavaPlugin implements PlanPlugin {
public void cancelAllTasks() { public void cancelAllTasks() {
runnableFactory.cancelAllKnownTasks(); runnableFactory.cancelAllKnownTasks();
Bukkit.getScheduler().cancelTasks(this); if (!isFolia()) {
Bukkit.getScheduler().cancelTasks(this);
}
}
private static boolean isFolia() {
try {
Class.forName("io.papermc.paper.threadedregions.RegionizedServer");
return true;
} catch (ClassNotFoundException e) {
return false;
}
} }
@Override @Override

View File

@ -16,6 +16,7 @@
*/ */
package com.djrapitops.plan.gathering.listeners.bukkit; package com.djrapitops.plan.gathering.listeners.bukkit;
import com.djrapitops.plan.gathering.JoinAddressValidator;
import com.djrapitops.plan.gathering.cache.JoinAddressCache; import com.djrapitops.plan.gathering.cache.JoinAddressCache;
import com.djrapitops.plan.gathering.domain.BukkitPlayerData; import com.djrapitops.plan.gathering.domain.BukkitPlayerData;
import com.djrapitops.plan.gathering.domain.event.PlayerJoin; import com.djrapitops.plan.gathering.domain.event.PlayerJoin;
@ -28,6 +29,8 @@ import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.storage.database.DBSystem; import com.djrapitops.plan.storage.database.DBSystem;
import com.djrapitops.plan.storage.database.transactions.events.BanStatusTransaction; import com.djrapitops.plan.storage.database.transactions.events.BanStatusTransaction;
import com.djrapitops.plan.storage.database.transactions.events.KickStoreTransaction; import com.djrapitops.plan.storage.database.transactions.events.KickStoreTransaction;
import com.djrapitops.plan.storage.database.transactions.events.StoreAllowlistBounceTransaction;
import com.djrapitops.plan.utilities.dev.Untrusted;
import com.djrapitops.plan.utilities.logging.ErrorContext; import com.djrapitops.plan.utilities.logging.ErrorContext;
import com.djrapitops.plan.utilities.logging.ErrorLogger; import com.djrapitops.plan.utilities.logging.ErrorLogger;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
@ -50,6 +53,7 @@ public class PlayerOnlineListener implements Listener {
private final PlayerJoinEventConsumer playerJoinEventConsumer; private final PlayerJoinEventConsumer playerJoinEventConsumer;
private final PlayerLeaveEventConsumer playerLeaveEventConsumer; private final PlayerLeaveEventConsumer playerLeaveEventConsumer;
private final JoinAddressValidator joinAddressValidator;
private final JoinAddressCache joinAddressCache; private final JoinAddressCache joinAddressCache;
private final ServerInfo serverInfo; private final ServerInfo serverInfo;
@ -61,6 +65,7 @@ public class PlayerOnlineListener implements Listener {
public PlayerOnlineListener( public PlayerOnlineListener(
PlayerJoinEventConsumer playerJoinEventConsumer, PlayerJoinEventConsumer playerJoinEventConsumer,
PlayerLeaveEventConsumer playerLeaveEventConsumer, PlayerLeaveEventConsumer playerLeaveEventConsumer,
JoinAddressValidator joinAddressValidator,
JoinAddressCache joinAddressCache, JoinAddressCache joinAddressCache,
ServerInfo serverInfo, ServerInfo serverInfo,
DBSystem dbSystem, DBSystem dbSystem,
@ -69,6 +74,7 @@ public class PlayerOnlineListener implements Listener {
) { ) {
this.playerJoinEventConsumer = playerJoinEventConsumer; this.playerJoinEventConsumer = playerJoinEventConsumer;
this.playerLeaveEventConsumer = playerLeaveEventConsumer; this.playerLeaveEventConsumer = playerLeaveEventConsumer;
this.joinAddressValidator = joinAddressValidator;
this.joinAddressCache = joinAddressCache; this.joinAddressCache = joinAddressCache;
this.serverInfo = serverInfo; this.serverInfo = serverInfo;
this.dbSystem = dbSystem; this.dbSystem = dbSystem;
@ -82,18 +88,14 @@ public class PlayerOnlineListener implements Listener {
UUID playerUUID = event.getPlayer().getUniqueId(); UUID playerUUID = event.getPlayer().getUniqueId();
ServerUUID serverUUID = serverInfo.getServerUUID(); ServerUUID serverUUID = serverInfo.getServerUUID();
boolean banned = PlayerLoginEvent.Result.KICK_BANNED == event.getResult(); boolean banned = PlayerLoginEvent.Result.KICK_BANNED == event.getResult();
boolean notWhitelisted = PlayerLoginEvent.Result.KICK_WHITELIST == event.getResult();
String address = event.getHostname(); if (notWhitelisted) {
if (!address.isEmpty()) { dbSystem.getDatabase().executeTransaction(new StoreAllowlistBounceTransaction(playerUUID, event.getPlayer().getName(), serverUUID, System.currentTimeMillis()));
if (address.contains(":")) { }
address = address.substring(0, address.lastIndexOf(':'));
} @Untrusted String address = joinAddressValidator.sanitize(event.getHostname());
if (address.contains("\u0000")) { if (joinAddressValidator.isValid(address)) {
address = address.substring(0, address.indexOf('\u0000'));
}
if (address.contains("fml")) {
address = address.substring(0, address.lastIndexOf("fml"));
}
joinAddressCache.put(playerUUID, address); joinAddressCache.put(playerUUID, address);
} }
dbSystem.getDatabase().executeTransaction(new BanStatusTransaction(playerUUID, serverUUID, banned)); dbSystem.getDatabase().executeTransaction(new BanStatusTransaction(playerUUID, serverUUID, banned));

View File

@ -89,9 +89,9 @@ public class BukkitPingCounter extends TaskSystem.Task implements Listener {
startRecording = new ConcurrentHashMap<>(); startRecording = new ConcurrentHashMap<>();
playerHistory = new HashMap<>(); playerHistory = new HashMap<>();
Optional<PingMethod> pingMethod = loadPingMethod(); Optional<PingMethod> loaded = loadPingMethod();
if (pingMethod.isPresent()) { if (loaded.isPresent()) {
this.pingMethod = pingMethod.get(); this.pingMethod = loaded.get();
pingMethodAvailable = true; pingMethodAvailable = true;
} else { } else {
pingMethodAvailable = false; pingMethodAvailable = false;

View File

@ -3,8 +3,8 @@ import org.apache.tools.ant.filters.ReplaceTokens
plugins { plugins {
id "dev.vankka.dependencydownload.plugin" version "$dependencyDownloadVersion" id "dev.vankka.dependencydownload.plugin" version "$dependencyDownloadVersion"
id "com.github.node-gradle.node" version "7.0.1" id "com.github.node-gradle.node" version "7.0.2"
id "io.swagger.core.v3.swagger-gradle-plugin" version "2.2.20" id "io.swagger.core.v3.swagger-gradle-plugin" version "2.2.22"
} }
configurations { configurations {
@ -56,7 +56,7 @@ dependencies {
shadow project(":extensions") shadow project(":extensions")
shadow "net.playeranalytics:platform-abstraction-layer-api:$palVersion" shadow "net.playeranalytics:platform-abstraction-layer-api:$palVersion"
compileOnly "net.kyori:adventure-api:4.14.0" compileOnly "net.kyori:adventure-api:$adventureVersion"
shadow("dev.vankka:dependencydownload-runtime:$dependencyDownloadVersion") { shadow("dev.vankka:dependencydownload-runtime:$dependencyDownloadVersion") {
// Effectively disables relocating // Effectively disables relocating
exclude module: "jar-relocator" exclude module: "jar-relocator"
@ -64,6 +64,7 @@ dependencies {
mysqlDriver "com.mysql:mysql-connector-j:$mysqlVersion" mysqlDriver "com.mysql:mysql-connector-j:$mysqlVersion"
mariadbDriver "org.mariadb.jdbc:mariadb-java-client:$mariadbVersion" mariadbDriver "org.mariadb.jdbc:mariadb-java-client:$mariadbVersion"
sqliteDriver "org.xerial:sqlite-jdbc:$sqliteVersion" sqliteDriver "org.xerial:sqlite-jdbc:$sqliteVersion"
sqliteDriver "org.slf4j:slf4j-nop:1.7.36"
ipAddressMatcher "com.github.seancfoley:ipaddress:$ipAddressMatcherVersion" ipAddressMatcher "com.github.seancfoley:ipaddress:$ipAddressMatcherVersion"
shadow "org.apache.commons:commons-text:$commonsTextVersion" shadow "org.apache.commons:commons-text:$commonsTextVersion"
@ -86,7 +87,7 @@ dependencies {
// Swagger annotations // Swagger annotations
implementation "jakarta.ws.rs:jakarta.ws.rs-api:3.1.0" implementation "jakarta.ws.rs:jakarta.ws.rs-api:4.0.0"
implementation "io.swagger.core.v3:swagger-core-jakarta:$swaggerVersion" implementation "io.swagger.core.v3:swagger-core-jakarta:$swaggerVersion"
implementation "io.swagger.core.v3:swagger-jaxrs2-jakarta:$swaggerVersion" implementation "io.swagger.core.v3:swagger-jaxrs2-jakarta:$swaggerVersion"
@ -94,12 +95,21 @@ dependencies {
testArtifacts project(":extensions:adventure") testArtifacts project(":extensions:adventure")
testImplementation project(":extensions:adventure") testImplementation project(":extensions:adventure")
testImplementation "com.google.code.gson:gson:$gsonVersion" testImplementation "com.google.code.gson:gson:$gsonVersion"
testImplementation "org.seleniumhq.selenium:selenium-java:4.12.1" testImplementation "org.seleniumhq.selenium:selenium-java:$seleniumVersion"
testImplementation "org.testcontainers:testcontainers:$testContainersVersion" testImplementation "org.testcontainers:testcontainers:$testContainersVersion"
testImplementation "org.testcontainers:junit-jupiter:$testContainersVersion" testImplementation "org.testcontainers:junit-jupiter:$testContainersVersion"
testImplementation "org.testcontainers:nginx:$testContainersVersion" testImplementation "org.testcontainers:nginx:$testContainersVersion"
} }
test {
environment "PLAN_TEST_NODE_STRING", "String"
environment "PLAN_TEST_NODE_BOOLEAN", "true"
environment "PLAN_TEST_NODE_INTEGER", "5"
environment "PLAN_TEST_NODE_DOUBLE", "0.5"
environment "PLAN_TEST_NODE_LONG", "9223372036854775807"
environment "PLAN_TEST_NODE_STRINGLIST", "- Test\n- Another"
}
task updateVersion(type: Copy) { task updateVersion(type: Copy) {
from('src/main/resources') { from('src/main/resources') {
include 'plugin.yml' include 'plugin.yml'
@ -232,8 +242,12 @@ artifacts {
} }
processResources { processResources {
dependsOn copyYarnBuildResults // Skips Yarn build on Jitpack since Jitpack doesn't offer gclib version compatible with Node 20
dependsOn determineAssetModifications // Jitpack build is used mainly for java dependencies.
if (!project.hasProperty("isJitpack")) {
dependsOn copyYarnBuildResults
dependsOn determineAssetModifications
}
dependsOn generateResourceForMySQLDriver dependsOn generateResourceForMySQLDriver
dependsOn generateResourceForSQLiteDriver dependsOn generateResourceForSQLiteDriver
dependsOn generateResourceForIpAddressMatcher dependsOn generateResourceForIpAddressMatcher

View File

@ -0,0 +1,119 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan;
import com.djrapitops.plan.component.ComponentSvc;
import com.djrapitops.plan.delivery.web.ResolverSvc;
import com.djrapitops.plan.delivery.web.ResourceSvc;
import com.djrapitops.plan.extension.ExtensionSvc;
import com.djrapitops.plan.query.QuerySvc;
import com.djrapitops.plan.settings.ListenerSvc;
import com.djrapitops.plan.settings.SchedulerSvc;
import com.djrapitops.plan.settings.SettingsSvc;
import javax.inject.Inject;
import javax.inject.Singleton;
/**
* Breaks up {@link PlanSystem} to be a smaller class.
*
* @author AuroraLS3
*/
@Singleton
public class ApiServices {
private final ComponentSvc componentService;
private final ResolverSvc resolverService;
private final ResourceSvc resourceService;
private final ExtensionSvc extensionService;
private final QuerySvc queryService;
private final ListenerSvc listenerService;
private final SettingsSvc settingsService;
private final SchedulerSvc schedulerService;
@Inject
public ApiServices(
ComponentSvc componentService,
ResolverSvc resolverService,
ResourceSvc resourceService,
ExtensionSvc extensionService,
QuerySvc queryService,
ListenerSvc listenerService,
SettingsSvc settingsService,
SchedulerSvc schedulerService
) {
this.componentService = componentService;
this.resolverService = resolverService;
this.resourceService = resourceService;
this.extensionService = extensionService;
this.queryService = queryService;
this.listenerService = listenerService;
this.settingsService = settingsService;
this.schedulerService = schedulerService;
}
public void register() {
extensionService.register();
componentService.register();
resolverService.register();
resourceService.register();
listenerService.register();
settingsService.register();
schedulerService.register();
queryService.register();
}
public void registerExtensions() {
extensionService.registerExtensions();
}
public void disableExtensionDataUpdates() {
extensionService.disableUpdates();
}
public ComponentSvc getComponentService() {
return componentService;
}
public ResolverSvc getResolverService() {
return resolverService;
}
public ResourceSvc getResourceService() {
return resourceService;
}
public ExtensionSvc getExtensionService() {
return extensionService;
}
public QuerySvc getQueryService() {
return queryService;
}
public ListenerSvc getListenerService() {
return listenerService;
}
public SettingsSvc getSettingsService() {
return settingsService;
}
public SchedulerSvc getSchedulerService() {
return schedulerService;
}
}

View File

@ -17,25 +17,17 @@
package com.djrapitops.plan; package com.djrapitops.plan;
import com.djrapitops.plan.api.PlanAPI; import com.djrapitops.plan.api.PlanAPI;
import com.djrapitops.plan.component.ComponentSvc;
import com.djrapitops.plan.delivery.DeliveryUtilities; import com.djrapitops.plan.delivery.DeliveryUtilities;
import com.djrapitops.plan.delivery.export.ExportSystem; import com.djrapitops.plan.delivery.export.ExportSystem;
import com.djrapitops.plan.delivery.formatting.Formatters; import com.djrapitops.plan.delivery.formatting.Formatters;
import com.djrapitops.plan.delivery.web.ResolverSvc;
import com.djrapitops.plan.delivery.web.ResourceSvc;
import com.djrapitops.plan.delivery.webserver.NonProxyWebserverDisableChecker; import com.djrapitops.plan.delivery.webserver.NonProxyWebserverDisableChecker;
import com.djrapitops.plan.delivery.webserver.WebServerSystem; import com.djrapitops.plan.delivery.webserver.WebServerSystem;
import com.djrapitops.plan.extension.ExtensionSvc;
import com.djrapitops.plan.gathering.cache.CacheSystem; import com.djrapitops.plan.gathering.cache.CacheSystem;
import com.djrapitops.plan.gathering.importing.ImportSystem; import com.djrapitops.plan.gathering.importing.ImportSystem;
import com.djrapitops.plan.gathering.listeners.ListenerSystem; import com.djrapitops.plan.gathering.listeners.ListenerSystem;
import com.djrapitops.plan.identification.ServerInfo; import com.djrapitops.plan.identification.ServerInfo;
import com.djrapitops.plan.processing.Processing; import com.djrapitops.plan.processing.Processing;
import com.djrapitops.plan.query.QuerySvc;
import com.djrapitops.plan.settings.ConfigSystem; import com.djrapitops.plan.settings.ConfigSystem;
import com.djrapitops.plan.settings.ListenerSvc;
import com.djrapitops.plan.settings.SchedulerSvc;
import com.djrapitops.plan.settings.SettingsSvc;
import com.djrapitops.plan.settings.locale.LocaleSystem; import com.djrapitops.plan.settings.locale.LocaleSystem;
import com.djrapitops.plan.storage.database.DBSystem; import com.djrapitops.plan.storage.database.DBSystem;
import com.djrapitops.plan.storage.file.PlanFiles; import com.djrapitops.plan.storage.file.PlanFiles;
@ -77,14 +69,7 @@ public class PlanSystem implements SubSystem {
private final ImportSystem importSystem; private final ImportSystem importSystem;
private final ExportSystem exportSystem; private final ExportSystem exportSystem;
private final DeliveryUtilities deliveryUtilities; private final DeliveryUtilities deliveryUtilities;
private final ComponentSvc componentService; private final ApiServices apiServices;
private final ResolverSvc resolverService;
private final ResourceSvc resourceService;
private final ExtensionSvc extensionService;
private final QuerySvc queryService;
private final ListenerSvc listenerService;
private final SettingsSvc settingsService;
private final SchedulerSvc schedulerService;
private final PluginLogger logger; private final PluginLogger logger;
private final ErrorLogger errorLogger; private final ErrorLogger errorLogger;
@ -104,16 +89,9 @@ public class PlanSystem implements SubSystem {
ImportSystem importSystem, ImportSystem importSystem,
ExportSystem exportSystem, ExportSystem exportSystem,
DeliveryUtilities deliveryUtilities, DeliveryUtilities deliveryUtilities,
ComponentSvc componentService,
ResolverSvc resolverService,
ResourceSvc resourceService,
ExtensionSvc extensionService,
QuerySvc queryService,
ListenerSvc listenerService,
SettingsSvc settingsService,
SchedulerSvc schedulerService,
PluginLogger logger, PluginLogger logger,
ErrorLogger errorLogger, ErrorLogger errorLogger,
ApiServices apiServices, // API v5
@SuppressWarnings("deprecation") PlanAPI.PlanAPIHolder apiHolder // Deprecated PlanAPI, backwards compatibility @SuppressWarnings("deprecation") PlanAPI.PlanAPIHolder apiHolder // Deprecated PlanAPI, backwards compatibility
) { ) {
this.files = files; this.files = files;
@ -130,16 +108,9 @@ public class PlanSystem implements SubSystem {
this.importSystem = importSystem; this.importSystem = importSystem;
this.exportSystem = exportSystem; this.exportSystem = exportSystem;
this.deliveryUtilities = deliveryUtilities; this.deliveryUtilities = deliveryUtilities;
this.componentService = componentService;
this.resolverService = resolverService;
this.resourceService = resourceService;
this.extensionService = extensionService;
this.queryService = queryService;
this.listenerService = listenerService;
this.settingsService = settingsService;
this.schedulerService = schedulerService;
this.logger = logger; this.logger = logger;
this.errorLogger = errorLogger; this.errorLogger = errorLogger;
this.apiServices = apiServices;
logger.info("§2"); logger.info("§2");
logger.info("§2 ██▌"); logger.info("§2 ██▌");
@ -162,14 +133,7 @@ public class PlanSystem implements SubSystem {
* Enables the rest of the systems that are not enabled in {@link #enableForCommands()}. * Enables the rest of the systems that are not enabled in {@link #enableForCommands()}.
*/ */
public void enableOtherThanCommands() { public void enableOtherThanCommands() {
extensionService.register(); apiServices.register();
componentService.register();
resolverService.register();
resourceService.register();
listenerService.register();
settingsService.register();
schedulerService.register();
queryService.register();
enableSystems( enableSystems(
processing, processing,
@ -193,7 +157,7 @@ public class PlanSystem implements SubSystem {
)); ));
} }
extensionService.registerExtensions(); apiServices.registerExtensions();
enabled = true; enabled = true;
String javaVersion = System.getProperty("java.specification.version"); String javaVersion = System.getProperty("java.specification.version");
@ -223,7 +187,7 @@ public class PlanSystem implements SubSystem {
enabled = false; enabled = false;
Formatters.clearSingleton(); Formatters.clearSingleton();
extensionService.disableUpdates(); apiServices.disableExtensionDataUpdates();
disableSystems( disableSystems(
taskSystem, taskSystem,
@ -316,12 +280,8 @@ public class PlanSystem implements SubSystem {
return enabled; return enabled;
} }
public ExtensionSvc getExtensionService() { public ApiServices getApiServices() {
return extensionService; return apiServices;
}
public ComponentSvc getComponentService() {
return componentService;
} }
public static long getServerEnableTime() { public static long getServerEnableTime() {

View File

@ -140,13 +140,21 @@ public class PlanCommand {
} }
public List<String> serverNames(CMDSender sender, @Untrusted Arguments arguments) { public List<String> serverNames(CMDSender sender, @Untrusted Arguments arguments) {
@Untrusted String asString = arguments.concatenate(" "); if (sender.hasPermission(Permissions.SERVER)) {
return tabCompleteCache.getMatchingServerIdentifiers(asString); @Untrusted String asString = arguments.concatenate(" ");
return tabCompleteCache.getMatchingServerIdentifiers(asString);
}
return List.of();
} }
private List<String> playerNames(CMDSender sender, @Untrusted Arguments arguments) { private List<String> playerNames(CMDSender sender, @Untrusted Arguments arguments) {
@Untrusted String asString = arguments.concatenate(" "); if (sender.hasPermission(Permissions.PLAYER_OTHER)) {
return tabCompleteCache.getMatchingPlayerIdentifiers(asString); @Untrusted String asString = arguments.concatenate(" ");
return tabCompleteCache.getMatchingPlayerIdentifiers(asString);
} else if (sender.hasPermission(Permissions.PLAYER_SELF)) {
return sender.getPlayerName().map(List::of).orElse(List.of());
}
return List.of();
} }
private Subcommand serverCommand() { private Subcommand serverCommand() {
@ -403,6 +411,9 @@ public class PlanCommand {
} }
private List<String> getBackupFilenames(CMDSender sender, @Untrusted Arguments arguments) { private List<String> getBackupFilenames(CMDSender sender, @Untrusted Arguments arguments) {
if (!sender.hasPermission(Permissions.DATA_RESTORE)) {
return List.of();
}
if (arguments.get(1).isPresent()) { if (arguments.get(1).isPresent()) {
return DBType.names(); return DBType.names();
} }
@ -531,6 +542,9 @@ public class PlanCommand {
} }
private List<String> webGroupTabComplete(CMDSender sender, @Untrusted Arguments arguments) { private List<String> webGroupTabComplete(CMDSender sender, @Untrusted Arguments arguments) {
if (!sender.hasPermission(Permissions.SET_GROUP)) {
return List.of();
}
Optional<String> groupArgument = arguments.get(1); Optional<String> groupArgument = arguments.get(1);
if (groupArgument.isPresent()) { if (groupArgument.isPresent()) {
return tabCompleteCache.getMatchingWebGroupNames(groupArgument.get()); return tabCompleteCache.getMatchingWebGroupNames(groupArgument.get());

View File

@ -29,6 +29,7 @@ import com.djrapitops.plan.delivery.formatting.Formatter;
import com.djrapitops.plan.delivery.formatting.Formatters; import com.djrapitops.plan.delivery.formatting.Formatters;
import com.djrapitops.plan.exceptions.ExportException; import com.djrapitops.plan.exceptions.ExportException;
import com.djrapitops.plan.gathering.domain.GeoInfo; import com.djrapitops.plan.gathering.domain.GeoInfo;
import com.djrapitops.plan.gathering.domain.event.JoinAddress;
import com.djrapitops.plan.gathering.importing.ImportSystem; import com.djrapitops.plan.gathering.importing.ImportSystem;
import com.djrapitops.plan.gathering.importing.importers.Importer; import com.djrapitops.plan.gathering.importing.importers.Importer;
import com.djrapitops.plan.identification.Identifiers; import com.djrapitops.plan.identification.Identifiers;
@ -42,6 +43,7 @@ import com.djrapitops.plan.settings.locale.Locale;
import com.djrapitops.plan.settings.locale.lang.CommandLang; import com.djrapitops.plan.settings.locale.lang.CommandLang;
import com.djrapitops.plan.settings.locale.lang.GenericLang; import com.djrapitops.plan.settings.locale.lang.GenericLang;
import com.djrapitops.plan.settings.locale.lang.HelpLang; import com.djrapitops.plan.settings.locale.lang.HelpLang;
import com.djrapitops.plan.settings.locale.lang.HtmlLang;
import com.djrapitops.plan.storage.database.DBSystem; import com.djrapitops.plan.storage.database.DBSystem;
import com.djrapitops.plan.storage.database.Database; import com.djrapitops.plan.storage.database.Database;
import com.djrapitops.plan.storage.database.queries.containers.ContainerFetchQueries; import com.djrapitops.plan.storage.database.queries.containers.ContainerFetchQueries;
@ -258,12 +260,17 @@ public class DataUtilityCommands {
Optional<GeoInfo> mostRecentGeoInfo = new GeoInfoMutator(geoInfo).mostRecent(); Optional<GeoInfo> mostRecentGeoInfo = new GeoInfoMutator(geoInfo).mostRecent();
String geolocation = mostRecentGeoInfo.isPresent() ? mostRecentGeoInfo.get().getGeolocation() : "-"; String geolocation = mostRecentGeoInfo.isPresent() ? mostRecentGeoInfo.get().getGeolocation() : "-";
SessionsMutator sessionsMutator = SessionsMutator.forContainer(player); SessionsMutator sessionsMutator = SessionsMutator.forContainer(player);
String latestJoinAddress = sessionsMutator.latestSession()
.flatMap(session -> session.getExtraData(JoinAddress.class))
.map(JoinAddress::getAddress)
.orElse("-");
String table = locale.getString(CommandLang.HEADER_INSPECT, playerName) + '\n' + String table = locale.getString(CommandLang.HEADER_INSPECT, playerName) + '\n' +
locale.getString(CommandLang.INGAME_ACTIVITY_INDEX, activityIndex.getFormattedValue(formatters.decimals()), activityIndex.getGroup()) + '\n' + locale.getString(CommandLang.INGAME_ACTIVITY_INDEX, activityIndex.getFormattedValue(formatters.decimals()), activityIndex.getGroup()) + '\n' +
locale.getString(CommandLang.INGAME_REGISTERED, timestamp.apply(() -> registered)) + '\n' + locale.getString(CommandLang.INGAME_REGISTERED, timestamp.apply(() -> registered)) + '\n' +
locale.getString(CommandLang.INGAME_LAST_SEEN, timestamp.apply(() -> lastSeen)) + '\n' + locale.getString(CommandLang.INGAME_LAST_SEEN, timestamp.apply(() -> lastSeen)) + '\n' +
locale.getString(CommandLang.INGAME_GEOLOCATION, geolocation) + '\n' + locale.getString(CommandLang.INGAME_GEOLOCATION, geolocation) + '\n' +
" §2" + locale.getString(HtmlLang.LABEL_LABEL_JOIN_ADDRESS) + ": §f" + latestJoinAddress + '\n' +
locale.getString(CommandLang.INGAME_TIMES_KICKED, player.getValue(PlayerKeys.KICK_COUNT).orElse(0)) + '\n' + locale.getString(CommandLang.INGAME_TIMES_KICKED, player.getValue(PlayerKeys.KICK_COUNT).orElse(0)) + '\n' +
'\n' + '\n' +
locale.getString(CommandLang.INGAME_PLAYTIME, length.apply(sessionsMutator.toPlaytime())) + '\n' + locale.getString(CommandLang.INGAME_PLAYTIME, length.apply(sessionsMutator.toPlaytime())) + '\n' +

View File

@ -16,6 +16,8 @@
*/ */
package com.djrapitops.plan.delivery.domain; package com.djrapitops.plan.delivery.domain;
import java.util.Objects;
/** /**
* Object that has a value tied to a date. * Object that has a value tied to a date.
* *
@ -39,4 +41,25 @@ public class DateObj<T> implements DateHolder {
public T getValue() { public T getValue() {
return value; return value;
} }
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
DateObj<?> dateObj = (DateObj<?>) o;
return getDate() == dateObj.getDate() && Objects.equals(getValue(), dateObj.getValue());
}
@Override
public int hashCode() {
return Objects.hash(getDate(), getValue());
}
@Override
public String toString() {
return "DateObj{" +
"date=" + date +
", value=" + value +
'}';
}
} }

View File

@ -28,7 +28,7 @@ import java.util.Objects;
*/ */
public class JoinAddressCount implements Comparable<JoinAddressCount> { public class JoinAddressCount implements Comparable<JoinAddressCount> {
private final int count; private int count;
private String joinAddress; private String joinAddress;
public JoinAddressCount(Map.Entry<String, Integer> entry) { public JoinAddressCount(Map.Entry<String, Integer> entry) {
@ -52,6 +52,10 @@ public class JoinAddressCount implements Comparable<JoinAddressCount> {
return count; return count;
} }
public void setCount(int count) {
this.count = count;
}
@Override @Override
public int compareTo(@NotNull JoinAddressCount other) { public int compareTo(@NotNull JoinAddressCount other) {
return String.CASE_INSENSITIVE_ORDER.compare(this.joinAddress, other.joinAddress); return String.CASE_INSENSITIVE_ORDER.compare(this.joinAddress, other.joinAddress);

View File

@ -19,6 +19,9 @@ package com.djrapitops.plan.delivery.domain.auth;
import com.djrapitops.plan.settings.locale.lang.Lang; import com.djrapitops.plan.settings.locale.lang.Lang;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import java.util.Arrays;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.function.Supplier; import java.util.function.Supplier;
/** /**
@ -47,7 +50,8 @@ public enum WebPermission implements Supplier<String>, Lang {
PAGE_NETWORK_SESSIONS_LIST("See list of sessions"), PAGE_NETWORK_SESSIONS_LIST("See list of sessions"),
PAGE_NETWORK_JOIN_ADDRESSES("See Join Addresses -tab"), PAGE_NETWORK_JOIN_ADDRESSES("See Join Addresses -tab"),
PAGE_NETWORK_JOIN_ADDRESSES_GRAPHS("See Join Address graphs"), PAGE_NETWORK_JOIN_ADDRESSES_GRAPHS("See Join Address graphs"),
PAGE_NETWORK_JOIN_ADDRESSES_GRAPHS_PIE("See Latest Join Addresses graph"), @Deprecated
PAGE_NETWORK_JOIN_ADDRESSES_GRAPHS_PIE("See Latest Join Addresses graph", true),
PAGE_NETWORK_JOIN_ADDRESSES_GRAPHS_TIME("See Join Addresses over time graph"), PAGE_NETWORK_JOIN_ADDRESSES_GRAPHS_TIME("See Join Addresses over time graph"),
PAGE_NETWORK_RETENTION("See Player Retention -tab"), PAGE_NETWORK_RETENTION("See Player Retention -tab"),
PAGE_NETWORK_GEOLOCATIONS("See Geolocations tab"), PAGE_NETWORK_GEOLOCATIONS("See Geolocations tab"),
@ -82,7 +86,8 @@ public enum WebPermission implements Supplier<String>, Lang {
PAGE_SERVER_SESSIONS_LIST("See list of sessions"), PAGE_SERVER_SESSIONS_LIST("See list of sessions"),
PAGE_SERVER_JOIN_ADDRESSES("See Join Addresses -tab"), PAGE_SERVER_JOIN_ADDRESSES("See Join Addresses -tab"),
PAGE_SERVER_JOIN_ADDRESSES_GRAPHS("See Join Address graphs"), PAGE_SERVER_JOIN_ADDRESSES_GRAPHS("See Join Address graphs"),
PAGE_SERVER_JOIN_ADDRESSES_GRAPHS_PIE("See Latest Join Addresses graph"), @Deprecated
PAGE_SERVER_JOIN_ADDRESSES_GRAPHS_PIE("See Latest Join Addresses graph", true),
PAGE_SERVER_JOIN_ADDRESSES_GRAPHS_TIME("See Join Addresses over time graph"), PAGE_SERVER_JOIN_ADDRESSES_GRAPHS_TIME("See Join Addresses over time graph"),
PAGE_SERVER_RETENTION("See Player Retention -tab"), PAGE_SERVER_RETENTION("See Player Retention -tab"),
PAGE_SERVER_GEOLOCATIONS("See Geolocations tab"), PAGE_SERVER_GEOLOCATIONS("See Geolocations tab"),
@ -93,6 +98,7 @@ public enum WebPermission implements Supplier<String>, Lang {
PAGE_SERVER_PERFORMANCE_OVERVIEW("See Performance numbers"), PAGE_SERVER_PERFORMANCE_OVERVIEW("See Performance numbers"),
PAGE_SERVER_PLUGIN_HISTORY("See Plugin History"), PAGE_SERVER_PLUGIN_HISTORY("See Plugin History"),
PAGE_SERVER_PLUGINS("See Plugins -tabs of servers"), PAGE_SERVER_PLUGINS("See Plugins -tabs of servers"),
PAGE_SERVER_ALLOWLIST_BOUNCE("See list of Game allowlist bounces"),
PAGE_PLAYER("See all of player page"), PAGE_PLAYER("See all of player page"),
PAGE_PLAYER_OVERVIEW("See Player Overview -tab"), PAGE_PLAYER_OVERVIEW("See Player Overview -tab"),
@ -155,4 +161,23 @@ public enum WebPermission implements Supplier<String>, Lang {
public String getDefault() { public String getDefault() {
return description; return description;
} }
public static WebPermission[] nonDeprecatedValues() {
return Arrays.stream(values())
.filter(Predicate.not(WebPermission::isDeprecated))
.toArray(WebPermission[]::new);
}
public static Optional<WebPermission> findByPermission(String permission) {
String name = StringUtils.upperCase(permission).replace('.', '_');
try {
return Optional.of(valueOf(name));
} catch (IllegalArgumentException noSuchEnum) {
return Optional.empty();
}
}
public static boolean isDeprecated(String permission) {
return findByPermission(permission).map(WebPermission::isDeprecated).orElse(false);
}
} }

View File

@ -0,0 +1,82 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.delivery.domain.datatransfer;
import com.djrapitops.plan.utilities.dev.Untrusted;
import java.util.Objects;
import java.util.UUID;
/**
* Represents an event where player bounced off the whitelist.
*
* @author AuroraLS3
*/
public class AllowlistBounce {
private final UUID playerUUID;
@Untrusted
private final String playerName;
private final int count;
private final long lastTime;
public AllowlistBounce(UUID playerUUID, String playerName, int count, long lastTime) {
this.playerUUID = playerUUID;
this.playerName = playerName;
this.count = count;
this.lastTime = lastTime;
}
public UUID getPlayerUUID() {
return playerUUID;
}
public String getPlayerName() {
return playerName;
}
public int getCount() {
return count;
}
public long getLastTime() {
return lastTime;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AllowlistBounce bounce = (AllowlistBounce) o;
return getCount() == bounce.getCount() && getLastTime() == bounce.getLastTime() && Objects.equals(getPlayerUUID(), bounce.getPlayerUUID()) && Objects.equals(getPlayerName(), bounce.getPlayerName());
}
@Override
public int hashCode() {
return Objects.hash(getPlayerUUID(), getPlayerName(), getCount(), getLastTime());
}
@Override
public String toString() {
return "AllowlistBounce{" +
"playerUUID=" + playerUUID +
", playerName='" + playerName + '\'' +
", count=" + count +
", lastTime=" + lastTime +
'}';
}
}

View File

@ -0,0 +1,67 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.delivery.domain.datatransfer;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
/**
* Represents data returned by {@link com.djrapitops.plan.delivery.webserver.resolver.json.PlayerJoinAddressJSONResolver}.
*
* @author AuroraLS3
*/
public class PlayerJoinAddresses {
private final List<String> joinAddresses;
private final Map<UUID, String> joinAddressByPlayer;
public PlayerJoinAddresses(List<String> joinAddresses, Map<UUID, String> joinAddressByPlayer) {
this.joinAddresses = joinAddresses;
this.joinAddressByPlayer = joinAddressByPlayer;
}
public List<String> getJoinAddresses() {
return joinAddresses;
}
public Map<UUID, String> getJoinAddressByPlayer() {
return joinAddressByPlayer;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PlayerJoinAddresses that = (PlayerJoinAddresses) o;
return Objects.equals(getJoinAddresses(), that.getJoinAddresses()) && Objects.equals(getJoinAddressByPlayer(), that.getJoinAddressByPlayer());
}
@Override
public int hashCode() {
return Objects.hash(getJoinAddresses(), getJoinAddressByPlayer());
}
@Override
public String toString() {
return "PlayerJoinAddresses{" +
"joinAddresses=" + joinAddresses +
", joinAddressByPlayer=" + joinAddressByPlayer +
'}';
}
}

View File

@ -28,15 +28,15 @@ import java.util.Objects;
*/ */
public class ServerSpecificLineGraph { public class ServerSpecificLineGraph {
private final List<Double[]> points; private final List<Number[]> points;
private final ServerDto server; private final ServerDto server;
public ServerSpecificLineGraph(List<Double[]> points, ServerDto server) { public ServerSpecificLineGraph(List<Number[]> points, ServerDto server) {
this.points = points; this.points = points;
this.server = server; this.server = server;
} }
public List<Double[]> getPoints() { public List<Number[]> getPoints() {
return points; return points;
} }

View File

@ -166,6 +166,12 @@ public class Exporter extends FileExporter {
} }
public void exportReact() throws ExportException { public void exportReact() throws ExportException {
if (config.isFalse(ExportSettings.PLAYER_PAGES)
&& config.isFalse(ExportSettings.SERVER_PAGE)
&& config.isFalse(ExportSettings.PLAYERS_PAGE)) {
return;
}
Path toDirectory = config.getPageExportPath(); Path toDirectory = config.getPageExportPath();
try { try {

View File

@ -121,7 +121,6 @@ public class NetworkPageExporter extends FileExporter {
"graph?type=uniqueAndNew", "graph?type=uniqueAndNew",
"graph?type=hourlyUniqueAndNew", "graph?type=hourlyUniqueAndNew",
"graph?type=serverPie", "graph?type=serverPie",
"graph?type=joinAddressPie",
"graph?type=joinAddressByDay", "graph?type=joinAddressByDay",
"graph?type=activity", "graph?type=activity",
"graph?type=geolocation", "graph?type=geolocation",

View File

@ -100,6 +100,7 @@ public class ServerPageExporter extends FileExporter {
server + serverUUID + "/playerbase", server + serverUUID + "/playerbase",
server + serverUUID + "/join-addresses", server + serverUUID + "/join-addresses",
server + serverUUID + "/retention", server + serverUUID + "/retention",
server + serverUUID + "/allowlist",
server + serverUUID + "/players", server + serverUUID + "/players",
server + serverUUID + "/geolocations", server + serverUUID + "/geolocations",
server + serverUUID + "/performance", server + serverUUID + "/performance",
@ -137,7 +138,6 @@ public class ServerPageExporter extends FileExporter {
"graph?type=geolocation&server=" + serverUUID, "graph?type=geolocation&server=" + serverUUID,
"graph?type=uniqueAndNew&server=" + serverUUID, "graph?type=uniqueAndNew&server=" + serverUUID,
"graph?type=hourlyUniqueAndNew&server=" + serverUUID, "graph?type=hourlyUniqueAndNew&server=" + serverUUID,
"graph?type=joinAddressPie&server=" + serverUUID,
"graph?type=joinAddressByDay&server=" + serverUUID, "graph?type=joinAddressByDay&server=" + serverUUID,
"graph?type=serverCalendar&server=" + serverUUID, "graph?type=serverCalendar&server=" + serverUUID,
"graph?type=punchCard&server=" + serverUUID, "graph?type=punchCard&server=" + serverUUID,
@ -148,7 +148,8 @@ public class ServerPageExporter extends FileExporter {
"extensionData?server=" + serverUUID, "extensionData?server=" + serverUUID,
"serverIdentity?server=" + serverUUID, "serverIdentity?server=" + serverUUID,
"retention?server=" + serverUUID, "retention?server=" + serverUUID,
"joinAddresses?server=" + serverUUID "joinAddresses?server=" + serverUUID,
"gameAllowlistBounces?server=" + serverUUID
); );
} }

View File

@ -110,7 +110,11 @@ public class Contributors {
new Contributor("xlanyleeet", LANG), new Contributor("xlanyleeet", LANG),
new Contributor("Jumala9163", LANG), new Contributor("Jumala9163", LANG),
new Contributor("Dreeam-qwq", CODE), new Contributor("Dreeam-qwq", CODE),
new Contributor("jhqwqmc", LANG) new Contributor("jhqwqmc", LANG),
new Contributor("liuzhen932", LANG),
new Contributor("Sniper_TVmc", LANG),
new Contributor("mcmdev", CODE),
new Contributor("ZhangYuheng", CODE)
}; };
private Contributors() { private Contributors() {

View File

@ -18,6 +18,7 @@ package com.djrapitops.plan.delivery.rendering.json;
import com.djrapitops.plan.delivery.domain.DateObj; import com.djrapitops.plan.delivery.domain.DateObj;
import com.djrapitops.plan.delivery.domain.RetentionData; import com.djrapitops.plan.delivery.domain.RetentionData;
import com.djrapitops.plan.delivery.domain.datatransfer.PlayerJoinAddresses;
import com.djrapitops.plan.delivery.domain.datatransfer.ServerDto; import com.djrapitops.plan.delivery.domain.datatransfer.ServerDto;
import com.djrapitops.plan.delivery.domain.mutators.PlayerKillMutator; import com.djrapitops.plan.delivery.domain.mutators.PlayerKillMutator;
import com.djrapitops.plan.delivery.domain.mutators.SessionsMutator; import com.djrapitops.plan.delivery.domain.mutators.SessionsMutator;
@ -34,6 +35,7 @@ import com.djrapitops.plan.identification.Server;
import com.djrapitops.plan.identification.ServerInfo; import com.djrapitops.plan.identification.ServerInfo;
import com.djrapitops.plan.identification.ServerUUID; import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.settings.config.PlanConfig; import com.djrapitops.plan.settings.config.PlanConfig;
import com.djrapitops.plan.settings.config.paths.DataGatheringSettings;
import com.djrapitops.plan.settings.config.paths.DisplaySettings; import com.djrapitops.plan.settings.config.paths.DisplaySettings;
import com.djrapitops.plan.settings.config.paths.TimeSettings; import com.djrapitops.plan.settings.config.paths.TimeSettings;
import com.djrapitops.plan.settings.locale.Locale; import com.djrapitops.plan.settings.locale.Locale;
@ -48,6 +50,7 @@ import com.djrapitops.plan.storage.database.queries.analysis.PlayerRetentionQuer
import com.djrapitops.plan.storage.database.queries.objects.*; import com.djrapitops.plan.storage.database.queries.objects.*;
import com.djrapitops.plan.storage.database.queries.objects.playertable.NetworkTablePlayersQuery; import com.djrapitops.plan.storage.database.queries.objects.playertable.NetworkTablePlayersQuery;
import com.djrapitops.plan.storage.database.queries.objects.playertable.ServerTablePlayersQuery; import com.djrapitops.plan.storage.database.queries.objects.playertable.ServerTablePlayersQuery;
import com.djrapitops.plan.storage.database.sql.tables.JoinAddressTable;
import com.djrapitops.plan.utilities.comparators.SessionStartComparator; import com.djrapitops.plan.utilities.comparators.SessionStartComparator;
import com.djrapitops.plan.utilities.dev.Untrusted; import com.djrapitops.plan.utilities.dev.Untrusted;
import com.djrapitops.plan.utilities.java.Maps; import com.djrapitops.plan.utilities.java.Maps;
@ -160,14 +163,52 @@ public class JSONFactory {
return db.query(PlayerRetentionQueries.fetchRetentionData()); return db.query(PlayerRetentionQueries.fetchRetentionData());
} }
public Map<UUID, String> playerJoinAddresses(ServerUUID serverUUID) { private static void removeFiltered(Map<UUID, String> addressByPlayerUUID, List<String> filteredJoinAddresses) {
Database db = dbSystem.getDatabase(); if (filteredJoinAddresses.isEmpty() || filteredJoinAddresses.equals(List.of("play.example.com"))) return;
return db.query(JoinAddressQueries.latestJoinAddressesOfPlayers(serverUUID));
Set<UUID> toRemove = new HashSet<>();
// Remove filtered addresses from the data
for (Map.Entry<UUID, String> entry : addressByPlayerUUID.entrySet()) {
if (filteredJoinAddresses.contains(entry.getValue())) {
toRemove.add(entry.getKey());
}
}
for (UUID playerUUID : toRemove) {
addressByPlayerUUID.put(playerUUID, JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP);
}
} }
public Map<UUID, String> playerJoinAddresses() { public PlayerJoinAddresses playerJoinAddresses(ServerUUID serverUUID, boolean includeByPlayerMap) {
Database db = dbSystem.getDatabase(); Database db = dbSystem.getDatabase();
return db.query(JoinAddressQueries.latestJoinAddressesOfPlayers()); List<String> filteredJoinAddresses = config.get(DataGatheringSettings.FILTER_JOIN_ADDRESSES);
if (includeByPlayerMap) {
Map<UUID, String> addresses = db.query(JoinAddressQueries.latestJoinAddressesOfPlayers(serverUUID));
removeFiltered(addresses, filteredJoinAddresses);
return new PlayerJoinAddresses(
addresses.values().stream().distinct().sorted().collect(Collectors.toList()),
addresses
);
} else {
List<String> addresses = db.query(JoinAddressQueries.uniqueJoinAddresses(serverUUID));
addresses.removeAll(filteredJoinAddresses);
return new PlayerJoinAddresses(addresses, null);
}
}
public PlayerJoinAddresses playerJoinAddresses(boolean includeByPlayerMap) {
Database db = dbSystem.getDatabase();
List<String> filteredJoinAddresses = config.get(DataGatheringSettings.FILTER_JOIN_ADDRESSES);
List<String> unique = db.query(JoinAddressQueries.uniqueJoinAddresses());
unique.removeAll(filteredJoinAddresses);
if (includeByPlayerMap) {
Map<UUID, String> latest = db.query(JoinAddressQueries.latestJoinAddressesOfPlayers());
removeFiltered(latest, filteredJoinAddresses);
return new PlayerJoinAddresses(unique, latest);
} else {
return new PlayerJoinAddresses(unique, null);
}
} }
public List<Map<String, Object>> serverSessionsAsJSONMap(ServerUUID serverUUID) { public List<Map<String, Object>> serverSessionsAsJSONMap(ServerUUID serverUUID) {
@ -320,9 +361,9 @@ public class JSONFactory {
tableEntries.add(Maps.builder(String.class, Object.class) tableEntries.add(Maps.builder(String.class, Object.class)
.put("country", geolocation) .put("country", geolocation)
.put("avg_ping", formatters.decimals().apply(ping.getAverage()) + " ms") .put("avg_ping", ping.getAverage())
.put("min_ping", ping.getMin() + " ms") .put("min_ping", ping.getMin())
.put("max_ping", ping.getMax() + " ms") .put("max_ping", ping.getMax())
.build()); .build());
} }
return tableEntries; return tableEntries;

View File

@ -154,9 +154,9 @@ public class PlayerJSONCreator {
private Map<String, Object> createPingGraphJson(PlayerContainer player) { private Map<String, Object> createPingGraphJson(PlayerContainer player) {
PingGraph pingGraph = graphs.line().pingGraph(player.getUnsafe(PlayerKeys.PING)); PingGraph pingGraph = graphs.line().pingGraph(player.getUnsafe(PlayerKeys.PING));
return Maps.builder(String.class, Object.class) return Maps.builder(String.class, Object.class)
.put("min_ping_series", pingGraph.getMinGraph().getPoints()) .put("min_ping_series", pingGraph.getMinGraph().getPointArrays())
.put("avg_ping_series", pingGraph.getAvgGraph().getPoints()) .put("avg_ping_series", pingGraph.getAvgGraph().getPointArrays())
.put("max_ping_series", pingGraph.getMaxGraph().getPoints()) .put("max_ping_series", pingGraph.getMaxGraph().getPointArrays())
.put("colors", Maps.builder(String.class, String.class) .put("colors", Maps.builder(String.class, String.class)
.put("min", theme.getValue(ThemeVal.GRAPH_MIN_PING)) .put("min", theme.getValue(ThemeVal.GRAPH_MIN_PING))
.put("avg", theme.getValue(ThemeVal.GRAPH_AVG_PING)) .put("avg", theme.getValue(ThemeVal.GRAPH_AVG_PING))

View File

@ -16,7 +16,6 @@
*/ */
package com.djrapitops.plan.delivery.rendering.json; package com.djrapitops.plan.delivery.rendering.json;
import com.djrapitops.plan.delivery.domain.DateHolder;
import com.djrapitops.plan.delivery.domain.DateObj; import com.djrapitops.plan.delivery.domain.DateObj;
import com.djrapitops.plan.delivery.domain.mutators.TPSMutator; import com.djrapitops.plan.delivery.domain.mutators.TPSMutator;
import com.djrapitops.plan.delivery.formatting.Formatter; import com.djrapitops.plan.delivery.formatting.Formatter;
@ -55,17 +54,14 @@ import java.util.concurrent.TimeUnit;
@Singleton @Singleton
public class ServerOverviewJSONCreator implements ServerTabJSONCreator<Map<String, Object>> { public class ServerOverviewJSONCreator implements ServerTabJSONCreator<Map<String, Object>> {
private final Formatter<Long> day;
private final PlanConfig config; private final PlanConfig config;
private final DBSystem dbSystem; private final DBSystem dbSystem;
private final ServerInfo serverInfo; private final ServerInfo serverInfo;
private final ServerSensor<?> serverSensor; private final ServerSensor<?> serverSensor;
private final Formatter<Long> timeAmount;
private final Formatter<Double> decimals; private final Formatter<Double> decimals;
private final Formatter<Double> percentage; private final Formatter<Double> percentage;
private final ServerUptimeCalculator serverUptimeCalculator; private final ServerUptimeCalculator serverUptimeCalculator;
private final Formatter<DateHolder> year;
@Inject @Inject
public ServerOverviewJSONCreator( public ServerOverviewJSONCreator(
@ -82,9 +78,6 @@ public class ServerOverviewJSONCreator implements ServerTabJSONCreator<Map<Strin
this.serverSensor = serverSensor; this.serverSensor = serverSensor;
this.serverUptimeCalculator = serverUptimeCalculator; this.serverUptimeCalculator = serverUptimeCalculator;
year = formatters.year();
day = formatters.dayLong();
timeAmount = formatters.timeAmount();
decimals = formatters.decimals(); decimals = formatters.decimals();
percentage = formatters.percentage(); percentage = formatters.percentage();
} }
@ -118,7 +111,7 @@ public class ServerOverviewJSONCreator implements ServerTabJSONCreator<Map<Strin
double averageTPS = tpsMutator.averageTPS(); double averageTPS = tpsMutator.averageTPS();
sevenDays.put("average_tps", averageTPS != -1 ? decimals.apply(averageTPS) : GenericLang.UNAVAILABLE.getKey()); sevenDays.put("average_tps", averageTPS != -1 ? decimals.apply(averageTPS) : GenericLang.UNAVAILABLE.getKey());
sevenDays.put("low_tps_spikes", tpsMutator.lowTpsSpikeCount(config.get(DisplaySettings.GRAPH_TPS_THRESHOLD_MED))); sevenDays.put("low_tps_spikes", tpsMutator.lowTpsSpikeCount(config.get(DisplaySettings.GRAPH_TPS_THRESHOLD_MED)));
sevenDays.put("downtime", timeAmount.apply(tpsMutator.serverDownTime())); sevenDays.put("downtime", tpsMutator.serverDownTime());
return sevenDays; return sevenDays;
} }
@ -137,18 +130,19 @@ public class ServerOverviewJSONCreator implements ServerTabJSONCreator<Map<Strin
numbers.put("online_players", getOnlinePlayers(serverUUID, db)); numbers.put("online_players", getOnlinePlayers(serverUUID, db));
Optional<DateObj<Integer>> lastPeak = db.query(TPSQueries.fetchPeakPlayerCount(serverUUID, twoDaysAgo)); Optional<DateObj<Integer>> lastPeak = db.query(TPSQueries.fetchPeakPlayerCount(serverUUID, twoDaysAgo));
Optional<DateObj<Integer>> allTimePeak = db.query(TPSQueries.fetchAllTimePeakPlayerCount(serverUUID)); Optional<DateObj<Integer>> allTimePeak = db.query(TPSQueries.fetchAllTimePeakPlayerCount(serverUUID));
numbers.put("last_peak_date", lastPeak.map(year).orElse("-")); numbers.put("last_peak_date", lastPeak.map(DateObj::getDate).map(Object.class::cast).orElse("-"));
numbers.put("last_peak_players", lastPeak.map(dateObj -> dateObj.getValue().toString()).orElse("-")); numbers.put("last_peak_players", lastPeak.map(dateObj -> dateObj.getValue().toString()).orElse("-"));
numbers.put("best_peak_date", allTimePeak.map(year).orElse("-")); numbers.put("best_peak_date", allTimePeak.map(DateObj::getDate).map(Object.class::cast).orElse("-"));
numbers.put("best_peak_players", allTimePeak.map(dateObj -> dateObj.getValue().toString()).orElse("-")); numbers.put("best_peak_players", allTimePeak.map(dateObj -> dateObj.getValue().toString()).orElse("-"));
Long totalPlaytime = db.query(SessionQueries.playtime(0L, now, serverUUID)); Long totalPlaytime = db.query(SessionQueries.playtime(0L, now, serverUUID));
numbers.put("playtime", timeAmount.apply(totalPlaytime)); numbers.put("playtime", totalPlaytime);
numbers.put("player_playtime", userCount != 0 ? timeAmount.apply(totalPlaytime / userCount) : "-"); numbers.put("player_playtime", userCount != 0 ? totalPlaytime / userCount : "-");
numbers.put("sessions", db.query(SessionQueries.sessionCount(0L, now, serverUUID))); numbers.put("sessions", db.query(SessionQueries.sessionCount(0L, now, serverUUID)));
numbers.put("player_kills", db.query(KillQueries.playerKillCount(0L, now, serverUUID))); numbers.put("player_kills", db.query(KillQueries.playerKillCount(0L, now, serverUUID)));
numbers.put("mob_kills", db.query(KillQueries.mobKillCount(0L, now, serverUUID))); numbers.put("mob_kills", db.query(KillQueries.mobKillCount(0L, now, serverUUID)));
numbers.put("deaths", db.query(KillQueries.deathCount(0L, now, serverUUID))); numbers.put("deaths", db.query(KillQueries.deathCount(0L, now, serverUUID)));
numbers.put("current_uptime", serverUptimeCalculator.getServerUptimeMillis(serverUUID).map(timeAmount) numbers.put("current_uptime", serverUptimeCalculator.getServerUptimeMillis(serverUUID)
.map(Object.class::cast)
.orElse(GenericLang.UNAVAILABLE.getKey())); .orElse(GenericLang.UNAVAILABLE.getKey()));
return numbers; return numbers;
@ -171,9 +165,9 @@ public class ServerOverviewJSONCreator implements ServerTabJSONCreator<Map<Strin
Map<String, Object> weeks = new HashMap<>(); Map<String, Object> weeks = new HashMap<>();
weeks.put("start", day.apply(twoWeeksAgo)); weeks.put("start", twoWeeksAgo);
weeks.put("midpoint", day.apply(oneWeekAgo)); weeks.put("midpoint", oneWeekAgo);
weeks.put("end", day.apply(now)); weeks.put("end", now);
Integer uniqueBefore = db.query(PlayerCountQueries.uniquePlayerCount(twoWeeksAgo, oneWeekAgo, serverUUID)); Integer uniqueBefore = db.query(PlayerCountQueries.uniquePlayerCount(twoWeeksAgo, oneWeekAgo, serverUUID));
Integer uniqueAfter = db.query(PlayerCountQueries.uniquePlayerCount(oneWeekAgo, now, serverUUID)); Integer uniqueAfter = db.query(PlayerCountQueries.uniquePlayerCount(oneWeekAgo, now, serverUUID));
@ -199,9 +193,9 @@ public class ServerOverviewJSONCreator implements ServerTabJSONCreator<Map<Strin
Long playtimeAfter = db.query(SessionQueries.playtime(oneWeekAgo, now, serverUUID)); Long playtimeAfter = db.query(SessionQueries.playtime(oneWeekAgo, now, serverUUID));
long avgPlaytimeBefore = uniqueBefore != 0 ? playtimeBefore / uniqueBefore : 0L; long avgPlaytimeBefore = uniqueBefore != 0 ? playtimeBefore / uniqueBefore : 0L;
long avgPlaytimeAfter = uniqueAfter != 0 ? playtimeAfter / uniqueAfter : 0L; long avgPlaytimeAfter = uniqueAfter != 0 ? playtimeAfter / uniqueAfter : 0L;
Trend avgPlaytimeTrend = new Trend(avgPlaytimeBefore, avgPlaytimeAfter, false, timeAmount); Trend avgPlaytimeTrend = new Trend(avgPlaytimeBefore, avgPlaytimeAfter, false);
weeks.put("average_playtime_before", timeAmount.apply(avgPlaytimeBefore)); weeks.put("average_playtime_before", avgPlaytimeBefore);
weeks.put("average_playtime_after", timeAmount.apply(avgPlaytimeAfter)); weeks.put("average_playtime_after", avgPlaytimeAfter);
weeks.put("average_playtime_trend", avgPlaytimeTrend); weeks.put("average_playtime_trend", avgPlaytimeTrend);
Long sessionsBefore = db.query(SessionQueries.sessionCount(twoWeeksAgo, oneWeekAgo, serverUUID)); Long sessionsBefore = db.query(SessionQueries.sessionCount(twoWeeksAgo, oneWeekAgo, serverUUID));

View File

@ -32,7 +32,6 @@ import com.djrapitops.plan.delivery.rendering.json.graphs.line.LineGraphFactory;
import com.djrapitops.plan.delivery.rendering.json.graphs.line.PingGraph; import com.djrapitops.plan.delivery.rendering.json.graphs.line.PingGraph;
import com.djrapitops.plan.delivery.rendering.json.graphs.line.Point; import com.djrapitops.plan.delivery.rendering.json.graphs.line.Point;
import com.djrapitops.plan.delivery.rendering.json.graphs.pie.Pie; import com.djrapitops.plan.delivery.rendering.json.graphs.pie.Pie;
import com.djrapitops.plan.delivery.rendering.json.graphs.pie.PieSlice;
import com.djrapitops.plan.delivery.rendering.json.graphs.pie.WorldPie; import com.djrapitops.plan.delivery.rendering.json.graphs.pie.WorldPie;
import com.djrapitops.plan.delivery.rendering.json.graphs.special.WorldMap; import com.djrapitops.plan.delivery.rendering.json.graphs.special.WorldMap;
import com.djrapitops.plan.delivery.rendering.json.graphs.stack.StackGraph; import com.djrapitops.plan.delivery.rendering.json.graphs.stack.StackGraph;
@ -57,7 +56,7 @@ import com.djrapitops.plan.storage.database.queries.analysis.PlayerCountQueries;
import com.djrapitops.plan.storage.database.queries.objects.*; import com.djrapitops.plan.storage.database.queries.objects.*;
import com.djrapitops.plan.storage.database.sql.tables.JoinAddressTable; import com.djrapitops.plan.storage.database.sql.tables.JoinAddressTable;
import com.djrapitops.plan.utilities.comparators.DateHolderOldestComparator; import com.djrapitops.plan.utilities.comparators.DateHolderOldestComparator;
import com.djrapitops.plan.utilities.comparators.PieSliceComparator; import com.djrapitops.plan.utilities.dev.Untrusted;
import com.djrapitops.plan.utilities.java.Lists; import com.djrapitops.plan.utilities.java.Lists;
import com.djrapitops.plan.utilities.java.Maps; import com.djrapitops.plan.utilities.java.Maps;
import net.playeranalytics.plugin.scheduling.TimeAmount; import net.playeranalytics.plugin.scheduling.TimeAmount;
@ -457,34 +456,6 @@ public class GraphJSONCreator {
.build(); .build();
} }
public Map<String, Object> playerHostnamePieJSONAsMap() {
String[] pieColors = theme.getPieColors(ThemeVal.GRAPH_WORLD_PIE);
Map<String, Integer> joinAddresses = dbSystem.getDatabase().query(JoinAddressQueries.latestJoinAddresses());
translateUnknown(joinAddresses);
List<PieSlice> slices = graphs.pie().joinAddressPie(joinAddresses).getSlices();
slices.sort(new PieSliceComparator());
return Maps.builder(String.class, Object.class)
.put("colors", pieColors)
.put("slices", slices)
.build();
}
public Map<String, Object> playerHostnamePieJSONAsMap(ServerUUID serverUUID) {
String[] pieColors = theme.getPieColors(ThemeVal.GRAPH_WORLD_PIE);
Map<String, Integer> joinAddresses = dbSystem.getDatabase().query(JoinAddressQueries.latestJoinAddresses(serverUUID));
translateUnknown(joinAddresses);
List<PieSlice> slices = graphs.pie().joinAddressPie(joinAddresses).getSlices();
slices.sort(new PieSliceComparator());
return Maps.builder(String.class, Object.class)
.put("colors", pieColors)
.put("slices", slices)
.build();
}
public void translateUnknown(Map<String, Integer> joinAddresses) { public void translateUnknown(Map<String, Integer> joinAddresses) {
Integer unknown = joinAddresses.get(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP); Integer unknown = joinAddresses.get(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP);
if (unknown != null) { if (unknown != null) {
@ -493,33 +464,65 @@ public class GraphJSONCreator {
} }
} }
public Map<String, Object> joinAddressesByDay(ServerUUID serverUUID, long after, long before) { public Map<String, Object> joinAddressesByDay(ServerUUID serverUUID, long after, long before, @Untrusted List<String> addressFilter) {
String[] pieColors = theme.getPieColors(ThemeVal.GRAPH_WORLD_PIE); String[] pieColors = theme.getPieColors(ThemeVal.GRAPH_WORLD_PIE);
List<DateObj<Map<String, Integer>>> joinAddresses = dbSystem.getDatabase().query(JoinAddressQueries.joinAddressesPerDay(serverUUID, config.getTimeZone().getOffset(System.currentTimeMillis()), after, before)); List<DateObj<Map<String, Integer>>> joinAddresses = dbSystem.getDatabase().query(JoinAddressQueries.joinAddressesPerDay(serverUUID, config.getTimeZone().getOffset(System.currentTimeMillis()), after, before, addressFilter));
return mapToJson(pieColors, joinAddresses); return mapToJson(pieColors, joinAddresses);
} }
public Map<String, Object> joinAddressesByDay(long after, long before) { public Map<String, Object> joinAddressesByDay(long after, long before, @Untrusted List<String> addressFilter) {
String[] pieColors = theme.getPieColors(ThemeVal.GRAPH_WORLD_PIE); String[] pieColors = theme.getPieColors(ThemeVal.GRAPH_WORLD_PIE);
List<DateObj<Map<String, Integer>>> joinAddresses = dbSystem.getDatabase().query(JoinAddressQueries.joinAddressesPerDay(config.getTimeZone().getOffset(System.currentTimeMillis()), after, before)); List<DateObj<Map<String, Integer>>> joinAddresses = dbSystem.getDatabase().query(JoinAddressQueries.joinAddressesPerDay(config.getTimeZone().getOffset(System.currentTimeMillis()), after, before, addressFilter));
return mapToJson(pieColors, joinAddresses); return mapToJson(pieColors, joinAddresses);
} }
private static void removeFilteredAddresses(List<JoinAddressCount> addresses, List<String> filteredJoinAddresses) {
if (filteredJoinAddresses.isEmpty() || filteredJoinAddresses.equals(List.of("play.example.com"))) return;
List<JoinAddressCount> addressesToRemove = addresses.stream()
.filter(address -> filteredJoinAddresses.contains(address.getJoinAddress()))
.collect(Collectors.toList());
if (!addressesToRemove.isEmpty()) {
Optional<JoinAddressCount> foundUnknownAddressCount = addresses.stream()
.filter(address -> address.getJoinAddress().equals(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP))
.findFirst();
JoinAddressCount unknownAddressCount;
if (foundUnknownAddressCount.isEmpty()) {
unknownAddressCount = new JoinAddressCount(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP, 0);
addresses.add(unknownAddressCount);
} else {
unknownAddressCount = foundUnknownAddressCount.get();
}
for (JoinAddressCount toRemove : addressesToRemove) {
unknownAddressCount.setCount(unknownAddressCount.getCount() + toRemove.getCount());
addresses.remove(toRemove);
}
}
}
private Map<String, Object> mapToJson(String[] pieColors, List<DateObj<Map<String, Integer>>> joinAddresses) { private Map<String, Object> mapToJson(String[] pieColors, List<DateObj<Map<String, Integer>>> joinAddresses) {
for (DateObj<Map<String, Integer>> addressesByDate : joinAddresses) { for (DateObj<Map<String, Integer>> addressesByDate : joinAddresses) {
translateUnknown(addressesByDate.getValue()); translateUnknown(addressesByDate.getValue());
} }
List<String> filteredJoinAddresses = config.get(DataGatheringSettings.FILTER_JOIN_ADDRESSES);
List<JoinAddressCounts> joinAddressCounts = joinAddresses.stream() List<JoinAddressCounts> joinAddressCounts = joinAddresses.stream()
.map(addressesOnDay -> new JoinAddressCounts( .map(addressesOnDay -> {
addressesOnDay.getDate(), List<JoinAddressCount> addresses = addressesOnDay.getValue().entrySet()
addressesOnDay.getValue().entrySet() .stream()
.stream() .map(JoinAddressCount::new)
.map(JoinAddressCount::new) .sorted()
.sorted() .collect(Collectors.toList());
.collect(Collectors.toList())))
removeFilteredAddresses(addresses, filteredJoinAddresses);
return new JoinAddressCounts(addressesOnDay.getDate(), addresses);
})
.sorted(new DateHolderOldestComparator()) .sorted(new DateHolderOldestComparator())
.collect(Collectors.toList()); .collect(Collectors.toList());
@ -537,7 +540,7 @@ public class GraphJSONCreator {
List<ServerSpecificLineGraph> proxyGraphs = new ArrayList<>(); List<ServerSpecificLineGraph> proxyGraphs = new ArrayList<>();
for (Server proxy : db.query(ServerQueries.fetchProxyServers())) { for (Server proxy : db.query(ServerQueries.fetchProxyServers())) {
ServerUUID proxyUUID = proxy.getUuid(); ServerUUID proxyUUID = proxy.getUuid();
List<Double[]> points = Lists.map( List<Number[]> points = Lists.map(
db.query(TPSQueries.fetchPlayersOnlineOfServer(halfYearAgo, now, proxyUUID)), db.query(TPSQueries.fetchPlayersOnlineOfServer(halfYearAgo, now, proxyUUID)),
point -> Point.fromDateObj(point).toArray() point -> Point.fromDateObj(point).toArray()
); );

View File

@ -21,6 +21,7 @@ import com.djrapitops.plan.delivery.rendering.json.graphs.HighChart;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/** /**
* This is a LineGraph for any set of Points, thus it is Abstract. * This is a LineGraph for any set of Points, thus it is Abstract.
@ -80,6 +81,10 @@ public class LineGraph implements HighChart {
return points; return points;
} }
public List<Number[]> getPointArrays() {
return getPoints().stream().map(Point::toArray).collect(Collectors.toList());
}
private void addMissingPoints(StringBuilder arrayBuilder, Long lastX, long date) { private void addMissingPoints(StringBuilder arrayBuilder, Long lastX, long date) {
long iterate = lastX + gapStrategy.diffToFirstGapPointMs; long iterate = lastX + gapStrategy.diffToFirstGapPointMs;
while (iterate < date) { while (iterate < date) {

View File

@ -75,7 +75,7 @@ public class Point {
"y=" + y + '}'; "y=" + y + '}';
} }
public Double[] toArray() { public Number[] toArray() {
return new Double[]{x, y}; return new Number[]{x, y};
} }
} }

View File

@ -1,38 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.delivery.rendering.json.graphs.pie;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class JoinAddressPie extends Pie {
JoinAddressPie(Map<String, Integer> joinAddresses) {
super(turnToSlices(joinAddresses));
}
private static List<PieSlice> turnToSlices(Map<String, Integer> joinAddresses) {
List<PieSlice> slices = new ArrayList<>();
for (Map.Entry<String, Integer> address : joinAddresses.entrySet()) {
String joinAddress = address.getKey();
Integer total = address.getValue();
slices.add(new PieSlice(joinAddress, total));
}
return slices;
}
}

View File

@ -68,10 +68,6 @@ public class PieGraphFactory {
return new ServerPreferencePie(playtimeByServerName); return new ServerPreferencePie(playtimeByServerName);
} }
public Pie joinAddressPie(Map<String, Integer> joinAddresses) {
return new JoinAddressPie(joinAddresses);
}
public WorldPie worldPie(WorldTimes worldTimes) { public WorldPie worldPie(WorldTimes worldTimes) {
WorldAliasSettings worldAliasSettings = config.getWorldAliasSettings(); WorldAliasSettings worldAliasSettings = config.getWorldAliasSettings();
Map<String, Long> playtimePerAlias = worldAliasSettings.getPlaytimePerAlias(worldTimes); Map<String, Long> playtimePerAlias = worldAliasSettings.getPlaytimePerAlias(worldTimes);

View File

@ -16,9 +16,7 @@
*/ */
package com.djrapitops.plan.delivery.rendering.json.network; package com.djrapitops.plan.delivery.rendering.json.network;
import com.djrapitops.plan.delivery.domain.DateHolder;
import com.djrapitops.plan.delivery.domain.DateObj; import com.djrapitops.plan.delivery.domain.DateObj;
import com.djrapitops.plan.delivery.formatting.Formatter;
import com.djrapitops.plan.delivery.formatting.Formatters; import com.djrapitops.plan.delivery.formatting.Formatters;
import com.djrapitops.plan.delivery.rendering.json.Trend; import com.djrapitops.plan.delivery.rendering.json.Trend;
import com.djrapitops.plan.gathering.ServerSensor; import com.djrapitops.plan.gathering.ServerSensor;
@ -50,14 +48,11 @@ import java.util.concurrent.TimeUnit;
@Singleton @Singleton
public class NetworkOverviewJSONCreator implements NetworkTabJSONCreator<Map<String, Object>> { public class NetworkOverviewJSONCreator implements NetworkTabJSONCreator<Map<String, Object>> {
private final Formatter<Long> day;
private final PlanConfig config; private final PlanConfig config;
private final DBSystem dbSystem; private final DBSystem dbSystem;
private final ServerInfo serverInfo; private final ServerInfo serverInfo;
private final ServerSensor<?> serverSensor; private final ServerSensor<?> serverSensor;
private final Formatter<Long> timeAmount;
private final ServerUptimeCalculator serverUptimeCalculator; private final ServerUptimeCalculator serverUptimeCalculator;
private final Formatter<DateHolder> year;
@Inject @Inject
public NetworkOverviewJSONCreator( public NetworkOverviewJSONCreator(
@ -73,10 +68,6 @@ public class NetworkOverviewJSONCreator implements NetworkTabJSONCreator<Map<Str
this.serverInfo = serverInfo; this.serverInfo = serverInfo;
this.serverSensor = serverSensor; this.serverSensor = serverSensor;
this.serverUptimeCalculator = serverUptimeCalculator; this.serverUptimeCalculator = serverUptimeCalculator;
year = formatters.year();
day = formatters.dayLong();
timeAmount = formatters.timeAmount();
} }
public Map<String, Object> createJSONAsMap() { public Map<String, Object> createJSONAsMap() {
@ -122,17 +113,18 @@ public class NetworkOverviewJSONCreator implements NetworkTabJSONCreator<Map<Str
ServerUUID serverUUID = serverInfo.getServerUUID(); ServerUUID serverUUID = serverInfo.getServerUUID();
Optional<DateObj<Integer>> lastPeak = db.query(TPSQueries.fetchPeakPlayerCount(serverUUID, twoDaysAgo)); Optional<DateObj<Integer>> lastPeak = db.query(TPSQueries.fetchPeakPlayerCount(serverUUID, twoDaysAgo));
Optional<DateObj<Integer>> allTimePeak = db.query(TPSQueries.fetchAllTimePeakPlayerCount(serverUUID)); Optional<DateObj<Integer>> allTimePeak = db.query(TPSQueries.fetchAllTimePeakPlayerCount(serverUUID));
numbers.put("last_peak_date", lastPeak.map(year).orElse("-")); numbers.put("last_peak_date", lastPeak.map(DateObj::getDate).map(Object.class::cast).orElse("-"));
numbers.put("last_peak_players", lastPeak.map(dateObj -> dateObj.getValue().toString()).orElse("-")); numbers.put("last_peak_players", lastPeak.map(dateObj -> dateObj.getValue().toString()).orElse("-"));
numbers.put("best_peak_date", allTimePeak.map(year).orElse("-")); numbers.put("best_peak_date", allTimePeak.map(DateObj::getDate).map(Object.class::cast).orElse("-"));
numbers.put("best_peak_players", allTimePeak.map(dateObj -> dateObj.getValue().toString()).orElse("-")); numbers.put("best_peak_players", allTimePeak.map(dateObj -> dateObj.getValue().toString()).orElse("-"));
Long totalPlaytime = db.query(SessionQueries.playtime(0L, now)); Long totalPlaytime = db.query(SessionQueries.playtime(0L, now));
numbers.put("playtime", timeAmount.apply(totalPlaytime)); numbers.put("playtime", totalPlaytime);
numbers.put("player_playtime", userCount != 0 ? timeAmount.apply(totalPlaytime / userCount) : "-"); numbers.put("player_playtime", userCount != 0 ? totalPlaytime / userCount : "-");
Long sessionCount = db.query(SessionQueries.sessionCount(0L, now)); Long sessionCount = db.query(SessionQueries.sessionCount(0L, now));
numbers.put("sessions", sessionCount); numbers.put("sessions", sessionCount);
numbers.put("session_length_avg", sessionCount != 0 ? timeAmount.apply(totalPlaytime / sessionCount) : "-"); numbers.put("session_length_avg", sessionCount != 0 ? totalPlaytime / sessionCount : "-");
numbers.put("current_uptime", serverUptimeCalculator.getServerUptimeMillis(serverUUID).map(timeAmount) numbers.put("current_uptime", serverUptimeCalculator.getServerUptimeMillis(serverUUID)
.map(Object.class::cast)
.orElse(GenericLang.UNAVAILABLE.getKey())); .orElse(GenericLang.UNAVAILABLE.getKey()));
return numbers; return numbers;
@ -147,9 +139,9 @@ public class NetworkOverviewJSONCreator implements NetworkTabJSONCreator<Map<Str
Map<String, Object> weeks = new HashMap<>(); Map<String, Object> weeks = new HashMap<>();
weeks.put("start", day.apply(twoWeeksAgo)); weeks.put("start", twoWeeksAgo);
weeks.put("midpoint", day.apply(oneWeekAgo)); weeks.put("midpoint", oneWeekAgo);
weeks.put("end", day.apply(now)); weeks.put("end", now);
Integer uniqueBefore = db.query(PlayerCountQueries.uniquePlayerCount(twoWeeksAgo, oneWeekAgo)); Integer uniqueBefore = db.query(PlayerCountQueries.uniquePlayerCount(twoWeeksAgo, oneWeekAgo));
Integer uniqueAfter = db.query(PlayerCountQueries.uniquePlayerCount(oneWeekAgo, now)); Integer uniqueAfter = db.query(PlayerCountQueries.uniquePlayerCount(oneWeekAgo, now));
@ -175,9 +167,9 @@ public class NetworkOverviewJSONCreator implements NetworkTabJSONCreator<Map<Str
Long playtimeAfter = db.query(SessionQueries.playtime(oneWeekAgo, now)); Long playtimeAfter = db.query(SessionQueries.playtime(oneWeekAgo, now));
long avgPlaytimeBefore = uniqueBefore != 0 ? playtimeBefore / uniqueBefore : 0L; long avgPlaytimeBefore = uniqueBefore != 0 ? playtimeBefore / uniqueBefore : 0L;
long avgPlaytimeAfter = uniqueAfter != 0 ? playtimeAfter / uniqueAfter : 0L; long avgPlaytimeAfter = uniqueAfter != 0 ? playtimeAfter / uniqueAfter : 0L;
Trend avgPlaytimeTrend = new Trend(avgPlaytimeBefore, avgPlaytimeAfter, false, timeAmount); Trend avgPlaytimeTrend = new Trend(avgPlaytimeBefore, avgPlaytimeAfter, false);
weeks.put("average_playtime_before", timeAmount.apply(avgPlaytimeBefore)); weeks.put("average_playtime_before", avgPlaytimeBefore);
weeks.put("average_playtime_after", timeAmount.apply(avgPlaytimeAfter)); weeks.put("average_playtime_after", avgPlaytimeAfter);
weeks.put("average_playtime_trend", avgPlaytimeTrend); weeks.put("average_playtime_trend", avgPlaytimeTrend);
Long sessionsBefore = db.query(SessionQueries.sessionCount(twoWeeksAgo, oneWeekAgo)); Long sessionsBefore = db.query(SessionQueries.sessionCount(twoWeeksAgo, oneWeekAgo));
@ -189,9 +181,9 @@ public class NetworkOverviewJSONCreator implements NetworkTabJSONCreator<Map<Str
long avgSessionLengthBefore = sessionsBefore != 0 ? playtimeBefore / sessionsBefore : 0; long avgSessionLengthBefore = sessionsBefore != 0 ? playtimeBefore / sessionsBefore : 0;
long avgSessionLengthAfter = sessionsAfter != 0 ? playtimeAfter / sessionsAfter : 0; long avgSessionLengthAfter = sessionsAfter != 0 ? playtimeAfter / sessionsAfter : 0;
Trend avgSessionLengthTrend = new Trend(avgSessionLengthBefore, avgSessionLengthAfter, false, timeAmount); Trend avgSessionLengthTrend = new Trend(avgSessionLengthBefore, avgSessionLengthAfter, false);
weeks.put("session_length_average_before", timeAmount.apply(avgSessionLengthBefore)); weeks.put("session_length_average_before", avgSessionLengthBefore);
weeks.put("session_length_average_after", timeAmount.apply(avgSessionLengthAfter)); weeks.put("session_length_average_after", avgSessionLengthAfter);
weeks.put("session_length_average_trend", avgSessionLengthTrend); weeks.put("session_length_average_trend", avgSessionLengthTrend);
return weeks; return weeks;

View File

@ -0,0 +1,77 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.delivery.webserver;
import com.djrapitops.plan.utilities.dev.Untrusted;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
/**
* Simple guard against DDoS attacks to single endpoint.
* <p>
* This only protects against a DDoS that doesn't follow redirects.
*
* @author AuroraLS3
*/
public class RateLimitGuard {
private static final int ATTEMPT_LIMIT = 30;
private final Cache<String, Integer> requests = Caffeine.newBuilder()
.expireAfterWrite(120, TimeUnit.SECONDS)
.build();
private final Cache<String, String> lastRequestPath = Caffeine.newBuilder()
.expireAfterWrite(120, TimeUnit.SECONDS)
.build();
public boolean shouldPreventRequest(@Untrusted String requestPath, @Untrusted String accessor) {
String previous = lastRequestPath.getIfPresent(accessor);
if (!Objects.equals(previous, requestPath)) {
resetAttemptCount(accessor);
}
Integer attempts = requests.getIfPresent(accessor);
if (attempts == null) {
attempts = 0;
}
lastRequestPath.put(accessor, requestPath);
requests.put(accessor, attempts + 1);
// Too many attempts, forbid further attempts.
return attempts + 1 >= ATTEMPT_LIMIT;
}
public void resetAttemptCount(@Untrusted String accessor) {
// previous request changed
requests.invalidate(accessor);
requests.cleanUp();
}
public static class Disabled extends RateLimitGuard {
@Override
public boolean shouldPreventRequest(@Untrusted String requestedPath, String accessor) {
return false;
}
@Override
public void resetAttemptCount(String accessor) { /* Disabled */ }
}
}

View File

@ -475,6 +475,14 @@ public class ResponseFactory {
.build(); .build();
} }
public Response failedRateLimit403() {
return Response.builder()
.setMimeType(MimeType.HTML)
.setContent("<h1>403 Forbidden</h1><p>You are being rate-limited.</p>")
.setStatus(403)
.build();
}
public Response ipWhitelist403(@Untrusted String accessor) { public Response ipWhitelist403(@Untrusted String accessor) {
return Response.builder() return Response.builder()
.setMimeType(MimeType.HTML) .setMimeType(MimeType.HTML)

View File

@ -42,7 +42,6 @@ public enum DataID {
GRAPH_ACTIVITY, GRAPH_ACTIVITY,
GRAPH_PING, GRAPH_PING,
GRAPH_SERVER_PIE, GRAPH_SERVER_PIE,
GRAPH_HOSTNAME_PIE,
GRAPH_PUNCHCARD, GRAPH_PUNCHCARD,
SERVER_OVERVIEW, SERVER_OVERVIEW,
ONLINE_OVERVIEW, ONLINE_OVERVIEW,
@ -54,13 +53,28 @@ public enum DataID {
EXTENSION_TABS, EXTENSION_TABS,
EXTENSION_JSON, EXTENSION_JSON,
LIST_SERVERS, LIST_SERVERS,
JOIN_ADDRESSES_BY_DAY, JOIN_ADDRESSES_BY_DAY(false),
PLAYER_RETENTION, PLAYER_RETENTION,
PLAYER_JOIN_ADDRESSES, PLAYER_JOIN_ADDRESSES,
PLAYER_ALLOWLIST_BOUNCES,
; ;
private final boolean cacheable;
DataID() {
this(true);
}
DataID(boolean cacheable) {
this.cacheable = cacheable;
}
public boolean isCacheable() {
return cacheable;
}
public String of(ServerUUID serverUUID) { public String of(ServerUUID serverUUID) {
if (serverUUID == null) return name(); if (serverUUID == null) return name();
return name() + '-' + serverUUID; return name() + "_" + serverUUID;
} }
} }

View File

@ -74,6 +74,10 @@ public interface JSONStorage extends SubSystem {
this.timestamp = timestamp; this.timestamp = timestamp;
} }
public static StoredJSON fromObject(Object json, long timestamp) {
return new StoredJSON(new Gson().toJson(json), timestamp);
}
public String getJson() { public String getJson() {
return json; return json;
} }

View File

@ -72,7 +72,7 @@ public class WebserverLogMessages {
} }
public void warnWebserverDisabledByConfig() { public void warnWebserverDisabledByConfig() {
logger.warn(locale.getString(PluginLang.ENABLE_NOTIFY_WEB_SERVER_DISABLED)); logger.info(locale.getString(PluginLang.ENABLE_NOTIFY_WEB_SERVER_DISABLED));
} }
public void keystoreNotFoundError(InvalidPathException error, String keyStorePath) { public void keystoreNotFoundError(InvalidPathException error, String keyStorePath) {

View File

@ -75,8 +75,14 @@ public class AccessLogger {
} }
} }
try { try {
long timestamp = internalRequest.getTimestamp();
String accessAddress = internalRequest.getAccessAddress(webserverConfiguration);
String method = internalRequest.getMethod();
method = method != null ? method : "?";
String url = StoreRequestTransaction.getTruncatedURI(request, internalRequest);
int responseCode = response.getCode();
dbSystem.getDatabase().executeTransaction( dbSystem.getDatabase().executeTransaction(
new StoreRequestTransaction(webserverConfiguration, internalRequest, request, response) new StoreRequestTransaction(timestamp, accessAddress, method, url, responseCode)
); );
} catch (CompletionException | DBOpException e) { } catch (CompletionException | DBOpException e) {
errorLogger.warn(e, ErrorContext.builder() errorLogger.warn(e, ErrorContext.builder()

View File

@ -81,4 +81,6 @@ public interface InternalRequest {
} }
return authenticationExtractor.extractAuthentication(this); return authenticationExtractor.extractAuthentication(this);
} }
String getRequestedPath();
} }

View File

@ -129,6 +129,11 @@ public class JettyInternalRequest implements InternalRequest {
return baseRequest.getRequestURI(); return baseRequest.getRequestURI();
} }
@Override
public String getRequestedPath() {
return baseRequest.getHttpURI().getDecodedPath();
}
@Override @Override
public String toString() { public String toString() {
return "JettyInternalRequest{" + return "JettyInternalRequest{" +

View File

@ -19,6 +19,7 @@ package com.djrapitops.plan.delivery.webserver.http;
import com.djrapitops.plan.delivery.web.resolver.Response; import com.djrapitops.plan.delivery.web.resolver.Response;
import com.djrapitops.plan.delivery.web.resolver.request.Request; import com.djrapitops.plan.delivery.web.resolver.request.Request;
import com.djrapitops.plan.delivery.webserver.PassBruteForceGuard; import com.djrapitops.plan.delivery.webserver.PassBruteForceGuard;
import com.djrapitops.plan.delivery.webserver.RateLimitGuard;
import com.djrapitops.plan.delivery.webserver.ResponseFactory; import com.djrapitops.plan.delivery.webserver.ResponseFactory;
import com.djrapitops.plan.delivery.webserver.ResponseResolver; import com.djrapitops.plan.delivery.webserver.ResponseResolver;
import com.djrapitops.plan.delivery.webserver.auth.FailReason; import com.djrapitops.plan.delivery.webserver.auth.FailReason;
@ -40,6 +41,7 @@ public class RequestHandler {
private final ResponseResolver responseResolver; private final ResponseResolver responseResolver;
private final PassBruteForceGuard bruteForceGuard; private final PassBruteForceGuard bruteForceGuard;
private final RateLimitGuard rateLimitGuard;
private final AccessLogger accessLogger; private final AccessLogger accessLogger;
@Inject @Inject
@ -50,15 +52,22 @@ public class RequestHandler {
this.accessLogger = accessLogger; this.accessLogger = accessLogger;
bruteForceGuard = new PassBruteForceGuard(); bruteForceGuard = new PassBruteForceGuard();
rateLimitGuard = new RateLimitGuard();
} }
public Response getResponse(InternalRequest internalRequest) { public Response getResponse(InternalRequest internalRequest) {
@Untrusted String accessAddress = internalRequest.getAccessAddress(webserverConfiguration); @Untrusted String accessAddress = internalRequest.getAccessAddress(webserverConfiguration);
@Untrusted String requestedPath = internalRequest.getRequestedPath();
boolean blocked = false;
Response response; Response response;
@Untrusted Request request = null; @Untrusted Request request = null;
if (bruteForceGuard.shouldPreventRequest(accessAddress)) { if (bruteForceGuard.shouldPreventRequest(accessAddress)) {
response = responseFactory.failedLoginAttempts403(); response = responseFactory.failedLoginAttempts403();
blocked = true;
} else if (rateLimitGuard.shouldPreventRequest(requestedPath, accessAddress)) {
response = responseFactory.failedRateLimit403();
blocked = true;
} else if (!webserverConfiguration.getAllowedIpList().isAllowed(accessAddress)) { } else if (!webserverConfiguration.getAllowedIpList().isAllowed(accessAddress)) {
webserverConfiguration.getWebserverLogMessages() webserverConfiguration.getWebserverLogMessages()
.warnAboutWhitelistBlock(accessAddress, internalRequest.getRequestedURIString()); .warnAboutWhitelistBlock(accessAddress, internalRequest.getRequestedURIString());
@ -77,7 +86,9 @@ public class RequestHandler {
response.getHeaders().putIfAbsent("Access-Control-Allow-Credentials", "true"); response.getHeaders().putIfAbsent("Access-Control-Allow-Credentials", "true");
response.getHeaders().putIfAbsent("X-Robots-Tag", "noindex, nofollow"); response.getHeaders().putIfAbsent("X-Robots-Tag", "noindex, nofollow");
accessLogger.log(internalRequest, request, response); if (!blocked) {
accessLogger.log(internalRequest, request, response);
}
return response; return response;
} }

View File

@ -0,0 +1,119 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.delivery.webserver.resolver.json;
import com.djrapitops.plan.delivery.domain.auth.WebPermission;
import com.djrapitops.plan.delivery.formatting.Formatter;
import com.djrapitops.plan.delivery.web.resolver.MimeType;
import com.djrapitops.plan.delivery.web.resolver.Response;
import com.djrapitops.plan.delivery.web.resolver.request.Request;
import com.djrapitops.plan.delivery.web.resolver.request.WebUser;
import com.djrapitops.plan.delivery.webserver.cache.AsyncJSONResolverService;
import com.djrapitops.plan.delivery.webserver.cache.DataID;
import com.djrapitops.plan.delivery.webserver.cache.JSONStorage;
import com.djrapitops.plan.identification.Identifiers;
import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.storage.database.DBSystem;
import com.djrapitops.plan.storage.database.Database;
import com.djrapitops.plan.storage.database.queries.objects.AllowlistQueries;
import com.djrapitops.plan.storage.database.queries.objects.SessionQueries;
import com.djrapitops.plan.utilities.dev.Untrusted;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.ExampleObject;
import io.swagger.v3.oas.annotations.parameters.RequestBody;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import org.jetbrains.annotations.Nullable;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.util.Map;
import java.util.Optional;
/**
* Response resolver to get game allowlist bounces.
*
* @author AuroraLS3
*/
@Singleton
@Path("/v1/gameAllowlistBounces")
public class AllowlistJSONResolver extends JSONResolver {
private final DBSystem dbSystem;
private final Identifiers identifiers;
private final AsyncJSONResolverService jsonResolverService;
@Inject
public AllowlistJSONResolver(DBSystem dbSystem, Identifiers identifiers, AsyncJSONResolverService jsonResolverService) {
this.dbSystem = dbSystem;
this.identifiers = identifiers;
this.jsonResolverService = jsonResolverService;
}
@Override
public Formatter<Long> getHttpLastModifiedFormatter() {return jsonResolverService.getHttpLastModifiedFormatter();}
@Override
public boolean canAccess(@Untrusted Request request) {
WebUser user = request.getUser().orElse(new WebUser(""));
return user.hasPermission(WebPermission.PAGE_SERVER_ALLOWLIST_BOUNCE);
}
@GET
@Operation(
description = "Get allowlist bounce data for server",
responses = {
@ApiResponse(responseCode = "200", content = @Content(mediaType = MimeType.JSON)),
@ApiResponse(responseCode = "400", description = "If 'server' parameter is not an existing server")
},
parameters = @Parameter(in = ParameterIn.QUERY, name = "server", description = "Server identifier to get data for (optional)", examples = {
@ExampleObject("Server 1"),
@ExampleObject("1"),
@ExampleObject("1fb39d2a-eb82-4868-b245-1fad17d823b3"),
}),
requestBody = @RequestBody(content = @Content(examples = @ExampleObject()))
)
@Override
public Optional<Response> resolve(@Untrusted Request request) {
return Optional.of(getResponse(request));
}
private Response getResponse(@Untrusted Request request) {
JSONStorage.StoredJSON result = getStoredJSON(request);
return getCachedOrNewResponse(request, result);
}
@Nullable
private JSONStorage.StoredJSON getStoredJSON(Request request) {
Optional<Long> timestamp = Identifiers.getTimestamp(request);
ServerUUID serverUUID = identifiers.getServerUUID(request);
Database database = dbSystem.getDatabase();
return jsonResolverService.resolve(timestamp, DataID.PLAYER_ALLOWLIST_BOUNCES, serverUUID,
theUUID -> Map.of(
"allowlist_bounces", database.query(AllowlistQueries.getBounces(serverUUID)),
"last_seen_by_uuid", database.query(SessionQueries.lastSeen(serverUUID))
)
);
}
}

View File

@ -39,11 +39,16 @@ import io.swagger.v3.oas.annotations.parameters.RequestBody;
import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponse;
import jakarta.ws.rs.GET; import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path; import jakarta.ws.rs.Path;
import org.apache.commons.lang3.StringUtils;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.function.Function;
import java.util.function.Supplier;
/** /**
* Resolves /v1/graph JSON requests. * Resolves /v1/graph JSON requests.
@ -117,7 +122,6 @@ public class GraphsJSONResolver extends JSONResolver {
@ExampleObject("aggregatedPing"), @ExampleObject("aggregatedPing"),
@ExampleObject("punchCard"), @ExampleObject("punchCard"),
@ExampleObject("serverPie"), @ExampleObject("serverPie"),
@ExampleObject("joinAddressPie"),
@ExampleObject("joinAddressByDay"), @ExampleObject("joinAddressByDay"),
}), }),
@Parameter(in = ParameterIn.QUERY, name = "server", description = "Server identifier to get data for", examples = { @Parameter(in = ParameterIn.QUERY, name = "server", description = "Server identifier to get data for", examples = {
@ -156,15 +160,22 @@ public class GraphsJSONResolver extends JSONResolver {
JSONStorage.StoredJSON storedJSON; JSONStorage.StoredJSON storedJSON;
if (request.getQuery().get("server").isPresent()) { if (request.getQuery().get("server").isPresent()) {
ServerUUID serverUUID = identifiers.getServerUUID(request); // Can throw BadRequestException ServerUUID serverUUID = identifiers.getServerUUID(request); // Can throw BadRequestException
storedJSON = jsonResolverService.resolve( Function<ServerUUID, Object> generationFunction = theServerUUID -> generateGraphDataJSONOfType(dataID, theServerUUID, request.getQuery());
timestamp, dataID, serverUUID, if (dataID.isCacheable()) {
theServerUUID -> generateGraphDataJSONOfType(dataID, theServerUUID, request.getQuery()) storedJSON = jsonResolverService.resolve(timestamp, dataID, serverUUID, generationFunction);
); } else {
storedJSON = JSONStorage.StoredJSON.fromObject(generationFunction.apply(serverUUID), System.currentTimeMillis());
}
} else { } else {
// Assume network // Assume network
storedJSON = jsonResolverService.resolve( Supplier<Object> generationFunction = () -> generateGraphDataJSONOfType(dataID, request.getQuery());
timestamp, dataID, () -> generateGraphDataJSONOfType(dataID, request.getQuery()) if (dataID.isCacheable()) {
); storedJSON = jsonResolverService.resolve(
timestamp, dataID, generationFunction
);
} else {
storedJSON = JSONStorage.StoredJSON.fromObject(generationFunction.get(), System.currentTimeMillis());
}
} }
return storedJSON; return storedJSON;
} }
@ -197,8 +208,6 @@ public class GraphsJSONResolver extends JSONResolver {
return DataID.GRAPH_PUNCHCARD; return DataID.GRAPH_PUNCHCARD;
case "serverPie": case "serverPie":
return DataID.GRAPH_SERVER_PIE; return DataID.GRAPH_SERVER_PIE;
case "joinAddressPie":
return DataID.GRAPH_HOSTNAME_PIE;
case "joinAddressByDay": case "joinAddressByDay":
return DataID.JOIN_ADDRESSES_BY_DAY; return DataID.JOIN_ADDRESSES_BY_DAY;
default: default:
@ -229,8 +238,6 @@ public class GraphsJSONResolver extends JSONResolver {
return List.of(WebPermission.PAGE_SERVER_PLAYERBASE_GRAPHS); return List.of(WebPermission.PAGE_SERVER_PLAYERBASE_GRAPHS);
case GRAPH_WORLD_MAP: case GRAPH_WORLD_MAP:
return List.of(WebPermission.PAGE_SERVER_GEOLOCATIONS_MAP); return List.of(WebPermission.PAGE_SERVER_GEOLOCATIONS_MAP);
case GRAPH_HOSTNAME_PIE:
return List.of(WebPermission.PAGE_SERVER_JOIN_ADDRESSES_GRAPHS_PIE);
case JOIN_ADDRESSES_BY_DAY: case JOIN_ADDRESSES_BY_DAY:
return List.of(WebPermission.PAGE_SERVER_JOIN_ADDRESSES_GRAPHS_TIME); return List.of(WebPermission.PAGE_SERVER_JOIN_ADDRESSES_GRAPHS_TIME);
default: default:
@ -258,8 +265,6 @@ public class GraphsJSONResolver extends JSONResolver {
return List.of(WebPermission.PAGE_NETWORK_GEOLOCATIONS_MAP); return List.of(WebPermission.PAGE_NETWORK_GEOLOCATIONS_MAP);
case GRAPH_ONLINE_PROXIES: case GRAPH_ONLINE_PROXIES:
return List.of(WebPermission.PAGE_NETWORK_OVERVIEW_GRAPHS_ONLINE); return List.of(WebPermission.PAGE_NETWORK_OVERVIEW_GRAPHS_ONLINE);
case GRAPH_HOSTNAME_PIE:
return List.of(WebPermission.PAGE_NETWORK_JOIN_ADDRESSES_GRAPHS_PIE);
case JOIN_ADDRESSES_BY_DAY: case JOIN_ADDRESSES_BY_DAY:
return List.of(WebPermission.PAGE_NETWORK_JOIN_ADDRESSES_GRAPHS_TIME); return List.of(WebPermission.PAGE_NETWORK_JOIN_ADDRESSES_GRAPHS_TIME);
default: default:
@ -283,8 +288,6 @@ public class GraphsJSONResolver extends JSONResolver {
return graphJSON.serverCalendarJSON(serverUUID); return graphJSON.serverCalendarJSON(serverUUID);
case GRAPH_WORLD_PIE: case GRAPH_WORLD_PIE:
return graphJSON.serverWorldPieJSONAsMap(serverUUID); return graphJSON.serverWorldPieJSONAsMap(serverUUID);
case GRAPH_HOSTNAME_PIE:
return graphJSON.playerHostnamePieJSONAsMap(serverUUID);
case GRAPH_ACTIVITY: case GRAPH_ACTIVITY:
return graphJSON.activityGraphsJSONAsMap(serverUUID); return graphJSON.activityGraphsJSONAsMap(serverUUID);
case GRAPH_WORLD_MAP: case GRAPH_WORLD_MAP:
@ -294,19 +297,24 @@ public class GraphsJSONResolver extends JSONResolver {
case GRAPH_PUNCHCARD: case GRAPH_PUNCHCARD:
return graphJSON.punchCardJSONAsMap(serverUUID); return graphJSON.punchCardJSONAsMap(serverUUID);
case JOIN_ADDRESSES_BY_DAY: case JOIN_ADDRESSES_BY_DAY:
try { return joinAddressGraph(serverUUID, query);
return graphJSON.joinAddressesByDay(serverUUID,
query.get("after").map(Long::parseLong).orElse(0L),
query.get("before").map(Long::parseLong).orElse(System.currentTimeMillis())
);
} catch (@Untrusted NumberFormatException e) {
throw new BadRequestException("'after' or 'before' is not a epoch millisecond (number)");
}
default: default:
throw new BadRequestException("Graph type not supported with server-parameter (" + id.name() + ")"); throw new BadRequestException("Graph type not supported with server-parameter (" + id.name() + ")");
} }
} }
private Map<String, Object> joinAddressGraph(ServerUUID serverUUID, @Untrusted URIQuery query) {
try {
Long after = query.get("after").map(Long::parseLong).orElse(0L);
Long before = query.get("before").map(Long::parseLong).orElse(System.currentTimeMillis());
@Untrusted List<String> addressFilter = query.get("addresses").map(s -> StringUtils.split(s, ','))
.map(Arrays::asList).orElse(List.of());
return graphJSON.joinAddressesByDay(serverUUID, after, before, addressFilter);
} catch (@Untrusted NumberFormatException e) {
throw new BadRequestException("'after' or 'before' is not a epoch millisecond (number)");
}
}
private Object generateGraphDataJSONOfType(DataID id, @Untrusted URIQuery query) { private Object generateGraphDataJSONOfType(DataID id, @Untrusted URIQuery query) {
switch (id) { switch (id) {
case GRAPH_ACTIVITY: case GRAPH_ACTIVITY:
@ -319,23 +327,26 @@ public class GraphsJSONResolver extends JSONResolver {
return graphJSON.networkCalendarJSON(); return graphJSON.networkCalendarJSON();
case GRAPH_SERVER_PIE: case GRAPH_SERVER_PIE:
return graphJSON.serverPreferencePieJSONAsMap(); return graphJSON.serverPreferencePieJSONAsMap();
case GRAPH_HOSTNAME_PIE:
return graphJSON.playerHostnamePieJSONAsMap();
case GRAPH_WORLD_MAP: case GRAPH_WORLD_MAP:
return graphJSON.geolocationGraphsJSONAsMap(); return graphJSON.geolocationGraphsJSONAsMap();
case GRAPH_ONLINE_PROXIES: case GRAPH_ONLINE_PROXIES:
return graphJSON.proxyPlayersOnlineGraphs(); return graphJSON.proxyPlayersOnlineGraphs();
case JOIN_ADDRESSES_BY_DAY: case JOIN_ADDRESSES_BY_DAY:
try { return joinAddressGraph(query);
return graphJSON.joinAddressesByDay(
query.get("after").map(Long::parseLong).orElse(0L),
query.get("before").map(Long::parseLong).orElse(System.currentTimeMillis())
);
} catch (@Untrusted NumberFormatException e) {
throw new BadRequestException("'after' or 'before' is not a epoch millisecond (number)");
}
default: default:
throw new BadRequestException("Graph type not supported without server-parameter (" + id.name() + ")"); throw new BadRequestException("Graph type not supported without server-parameter (" + id.name() + ")");
} }
} }
private Map<String, Object> joinAddressGraph(URIQuery query) {
try {
Long after = query.get("after").map(Long::parseLong).orElse(0L);
Long before = query.get("before").map(Long::parseLong).orElse(System.currentTimeMillis());
@Untrusted List<String> addressFilter = query.get("addresses").map(s -> StringUtils.split(s, ','))
.map(Arrays::asList).orElse(List.of());
return graphJSON.joinAddressesByDay(after, before, addressFilter);
} catch (@Untrusted NumberFormatException e) {
throw new BadRequestException("'after' or 'before' is not a epoch millisecond (number)");
}
}
} }

View File

@ -17,11 +17,13 @@
package com.djrapitops.plan.delivery.webserver.resolver.json; package com.djrapitops.plan.delivery.webserver.resolver.json;
import com.djrapitops.plan.delivery.domain.auth.WebPermission; import com.djrapitops.plan.delivery.domain.auth.WebPermission;
import com.djrapitops.plan.delivery.domain.datatransfer.PlayerJoinAddresses;
import com.djrapitops.plan.delivery.formatting.Formatter; import com.djrapitops.plan.delivery.formatting.Formatter;
import com.djrapitops.plan.delivery.rendering.json.JSONFactory; import com.djrapitops.plan.delivery.rendering.json.JSONFactory;
import com.djrapitops.plan.delivery.web.resolver.MimeType; import com.djrapitops.plan.delivery.web.resolver.MimeType;
import com.djrapitops.plan.delivery.web.resolver.Response; import com.djrapitops.plan.delivery.web.resolver.Response;
import com.djrapitops.plan.delivery.web.resolver.request.Request; import com.djrapitops.plan.delivery.web.resolver.request.Request;
import com.djrapitops.plan.delivery.web.resolver.request.URIQuery;
import com.djrapitops.plan.delivery.web.resolver.request.WebUser; import com.djrapitops.plan.delivery.web.resolver.request.WebUser;
import com.djrapitops.plan.delivery.webserver.cache.AsyncJSONResolverService; import com.djrapitops.plan.delivery.webserver.cache.AsyncJSONResolverService;
import com.djrapitops.plan.delivery.webserver.cache.DataID; import com.djrapitops.plan.delivery.webserver.cache.DataID;
@ -34,6 +36,7 @@ import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn; import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.ExampleObject; import io.swagger.v3.oas.annotations.media.ExampleObject;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.parameters.RequestBody; import io.swagger.v3.oas.annotations.parameters.RequestBody;
import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponse;
import jakarta.ws.rs.GET; import jakarta.ws.rs.GET;
@ -42,7 +45,6 @@ import org.jetbrains.annotations.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
import java.util.Collections;
import java.util.Optional; import java.util.Optional;
/** /**
@ -69,17 +71,27 @@ public class PlayerJoinAddressJSONResolver extends JSONResolver {
@Override @Override
public boolean canAccess(@Untrusted Request request) { public boolean canAccess(@Untrusted Request request) {
WebUser user = request.getUser().orElse(new WebUser("")); WebUser user = request.getUser().orElse(new WebUser(""));
if (request.getQuery().get("server").isPresent()) { @Untrusted URIQuery query = request.getQuery();
return user.hasPermission(WebPermission.PAGE_SERVER_RETENTION); Optional<String> listOnly = query.get("listOnly");
if (query.get("server").isPresent()) {
if (listOnly.isEmpty()) {
return user.hasPermission(WebPermission.PAGE_SERVER_RETENTION);
} else {
return user.hasPermission(WebPermission.PAGE_SERVER_JOIN_ADDRESSES_GRAPHS_TIME);
}
}
if (listOnly.isEmpty()) {
return user.hasPermission(WebPermission.PAGE_NETWORK_RETENTION);
} else {
return user.hasPermission(WebPermission.PAGE_NETWORK_JOIN_ADDRESSES_GRAPHS_TIME);
} }
return user.hasPermission(WebPermission.PAGE_NETWORK_RETENTION);
} }
@GET @GET
@Operation( @Operation(
description = "Get join address information of players for server or network", description = "Get join address information of players for server or network",
responses = { responses = {
@ApiResponse(responseCode = "200", content = @Content(mediaType = MimeType.JSON)), @ApiResponse(responseCode = "200", content = @Content(mediaType = MimeType.JSON, schema = @Schema(implementation = PlayerJoinAddresses.class))),
@ApiResponse(responseCode = "400", description = "If 'server' parameter is not an existing server") @ApiResponse(responseCode = "400", description = "If 'server' parameter is not an existing server")
}, },
parameters = @Parameter(in = ParameterIn.QUERY, name = "server", description = "Server identifier to get data for (optional)", examples = { parameters = @Parameter(in = ParameterIn.QUERY, name = "server", description = "Server identifier to get data for (optional)", examples = {
@ -105,12 +117,12 @@ public class PlayerJoinAddressJSONResolver extends JSONResolver {
if (request.getQuery().get("server").isPresent()) { if (request.getQuery().get("server").isPresent()) {
ServerUUID serverUUID = identifiers.getServerUUID(request); ServerUUID serverUUID = identifiers.getServerUUID(request);
return jsonResolverService.resolve(timestamp, DataID.PLAYER_JOIN_ADDRESSES, serverUUID, return jsonResolverService.resolve(timestamp, DataID.PLAYER_JOIN_ADDRESSES, serverUUID,
theUUID -> Collections.singletonMap("join_address_by_player", jsonFactory.playerJoinAddresses(theUUID)) serverUUID1 -> jsonFactory.playerJoinAddresses(serverUUID1, request.getQuery().get("listOnly").isEmpty())
); );
} }
// Assume network // Assume network
return jsonResolverService.resolve(timestamp, DataID.PLAYER_JOIN_ADDRESSES, return jsonResolverService.resolve(timestamp, DataID.PLAYER_JOIN_ADDRESSES,
() -> Collections.singletonMap("join_address_by_player", jsonFactory.playerJoinAddresses()) () -> jsonFactory.playerJoinAddresses(request.getQuery().get("listOnly").isEmpty())
); );
} }
} }

View File

@ -90,6 +90,7 @@ public class RootJSONResolver {
RetentionJSONResolver retentionJSONResolver, RetentionJSONResolver retentionJSONResolver,
PlayerJoinAddressJSONResolver playerJoinAddressJSONResolver, PlayerJoinAddressJSONResolver playerJoinAddressJSONResolver,
PluginHistoryJSONResolver pluginHistoryJSONResolver, PluginHistoryJSONResolver pluginHistoryJSONResolver,
AllowlistJSONResolver allowlistJSONResolver,
PreferencesJSONResolver preferencesJSONResolver, PreferencesJSONResolver preferencesJSONResolver,
StorePreferencesJSONResolver storePreferencesJSONResolver, StorePreferencesJSONResolver storePreferencesJSONResolver,
@ -129,7 +130,8 @@ public class RootJSONResolver {
.add("extensionData", extensionJSONResolver) .add("extensionData", extensionJSONResolver)
.add("retention", retentionJSONResolver) .add("retention", retentionJSONResolver)
.add("joinAddresses", playerJoinAddressJSONResolver) .add("joinAddresses", playerJoinAddressJSONResolver)
.add("preferences", preferencesJSONResolver); .add("preferences", preferencesJSONResolver)
.add("gameAllowlistBounces", allowlistJSONResolver);
this.webServer = webServer; this.webServer = webServer;
// These endpoints require authentication to be enabled. // These endpoints require authentication to be enabled.

View File

@ -114,7 +114,7 @@ public class FiltersJSONResolver implements Resolver {
)).build(); )).build();
} }
private List<Double[]> fetchViewGraphPoints() { private List<Number[]> fetchViewGraphPoints() {
List<DateObj<Integer>> data = dbSystem.getDatabase().query(TPSQueries.fetchViewPreviewGraphData(serverInfo.getServerUUID())); List<DateObj<Integer>> data = dbSystem.getDatabase().query(TPSQueries.fetchViewPreviewGraphData(serverInfo.getServerUUID()));
Long earliestStart = dbSystem.getDatabase().query(SessionQueries.earliestSessionStart()); Long earliestStart = dbSystem.getDatabase().query(SessionQueries.earliestSessionStart());
data.add(0, new DateObj<>(earliestStart, 1)); data.add(0, new DateObj<>(earliestStart, 1));
@ -136,9 +136,9 @@ public class FiltersJSONResolver implements Resolver {
class FilterResponseDto { class FilterResponseDto {
final List<FilterDto> filters; final List<FilterDto> filters;
final ViewDto view; final ViewDto view;
final List<Double[]> viewPoints; final List<Number[]> viewPoints;
public FilterResponseDto(Map<String, Filter> filtersByKind, ViewDto view, List<Double[]> viewPoints) { public FilterResponseDto(Map<String, Filter> filtersByKind, ViewDto view, List<Number[]> viewPoints) {
this.viewPoints = viewPoints; this.viewPoints = viewPoints;
this.filters = new ArrayList<>(); this.filters = new ArrayList<>();
for (Map.Entry<String, Filter> entry : filtersByKind.entrySet()) { for (Map.Entry<String, Filter> entry : filtersByKind.entrySet()) {

View File

@ -37,6 +37,8 @@ import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Collectors;
/** /**
* Endpoint for getting list of available Plan web permissions. * Endpoint for getting list of available Plan web permissions.
@ -75,7 +77,10 @@ public class WebPermissionJSONResolver implements Resolver {
} }
private Response getResponse() { private Response getResponse() {
List<String> permissions = dbSystem.getDatabase().query(WebUserQueries.fetchAvailablePermissions()); List<String> permissions = dbSystem.getDatabase().query(WebUserQueries.fetchAvailablePermissions())
.stream()
.filter(Predicate.not(WebPermission::isDeprecated))
.collect(Collectors.toList());
WebPermissionList permissionList = new WebPermissionList(permissions); WebPermissionList permissionList = new WebPermissionList(permissions);
return Response.builder() return Response.builder()

View File

@ -30,6 +30,7 @@ import java.util.Optional;
public class DBOpException extends IllegalStateException implements ExceptionWithContext { public class DBOpException extends IllegalStateException implements ExceptionWithContext {
public static final String CONSTRAINT_VIOLATION = "Constraint Violation"; public static final String CONSTRAINT_VIOLATION = "Constraint Violation";
public static final String DUPLICATE_KEY = "Duplicate key";
private final ErrorContext context; private final ErrorContext context;
public DBOpException(String message) { public DBOpException(String message) {
@ -77,7 +78,7 @@ public class DBOpException extends IllegalStateException implements ExceptionWit
case 1022: case 1022:
case 23001: case 23001:
case 23505: case 23505:
context.related("Duplicate key") context.related(DUPLICATE_KEY)
.whatToDo("Report this, duplicate key exists in SQL."); .whatToDo("Report this, duplicate key exists in SQL.");
break; break;
// Constraint violation // Constraint violation
@ -165,4 +166,9 @@ public class DBOpException extends IllegalStateException implements ExceptionWit
&& getCause() != null && getCause() != null
&& getCause().getMessage().contains("user_id"); && getCause().getMessage().contains("user_id");
} }
public boolean isDuplicateKeyViolation() {
return context != null
&& context.getRelated().contains(DBOpException.CONSTRAINT_VIOLATION);
}
} }

View File

@ -1,192 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.extension.implementation.storage.transactions.results;
import com.djrapitops.plan.storage.database.DBType;
import com.djrapitops.plan.storage.database.sql.tables.extension.*;
import com.djrapitops.plan.storage.database.transactions.ExecStatement;
import com.djrapitops.plan.storage.database.transactions.Executable;
import com.djrapitops.plan.storage.database.transactions.ThrowawayTransaction;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import static com.djrapitops.plan.storage.database.sql.building.Sql.*;
/**
* Transaction to remove older results that violate an updated condition value.
* <p>
* How it works:
* - Select all fulfilled conditions for all players (conditionName when true and not_conditionName when false)
* - Left join with player value and provider tables when uuids match, and when condition matches a condition in the query above.
* - Filter the join query for values where the condition did not match any provided condition in the join (Is null)
* - Delete all player values with IDs that are returned by the left join query after filtering
*
* @author AuroraLS3
*/
public class RemoveUnsatisfiedConditionalPlayerResultsTransaction extends ThrowawayTransaction {
private final String providerTable;
private final String playerValueTable;
private final String playerTableValueTable;
private final String tableTable;
private final String groupTable;
public RemoveUnsatisfiedConditionalPlayerResultsTransaction() {
providerTable = ExtensionProviderTable.TABLE_NAME;
playerValueTable = ExtensionPlayerValueTable.TABLE_NAME;
tableTable = ExtensionTableProviderTable.TABLE_NAME;
groupTable = ExtensionGroupsTable.TABLE_NAME;
playerTableValueTable = ExtensionPlayerTableValueTable.TABLE_NAME;
}
@Override
protected void performOperations() {
String selectSatisfiedConditions = getSatisfiedConditionsSQL();
execute(deleteUnsatisfiedValues(selectSatisfiedConditions));
execute(deleteUnsatisfiedGroupValues(selectSatisfiedConditions));
execute(deleteUnsatisfiedTableValues(selectSatisfiedConditions));
}
private String getSatisfiedConditionsSQL() {
String reversedCondition = dbType == DBType.SQLITE ? "'not_' || " + ExtensionProviderTable.PROVIDED_CONDITION : "CONCAT('not_'," + ExtensionProviderTable.PROVIDED_CONDITION + ')';
String selectSatisfiedPositiveConditions = SELECT +
ExtensionProviderTable.PROVIDED_CONDITION + ',' +
ExtensionProviderTable.PLUGIN_ID + ',' +
ExtensionPlayerTableValueTable.USER_UUID +
FROM + providerTable +
INNER_JOIN + playerValueTable + " on " + providerTable + '.' + ExtensionProviderTable.ID + "=" + ExtensionPlayerValueTable.PROVIDER_ID +
WHERE + ExtensionPlayerValueTable.BOOLEAN_VALUE + "=?" +
AND + ExtensionProviderTable.PROVIDED_CONDITION + IS_NOT_NULL;
String selectSatisfiedNegativeConditions = SELECT +
reversedCondition + " as " + ExtensionProviderTable.PROVIDED_CONDITION + ',' +
ExtensionProviderTable.PLUGIN_ID + ',' +
ExtensionPlayerTableValueTable.USER_UUID +
FROM + providerTable +
INNER_JOIN + playerValueTable + " on " + providerTable + '.' + ExtensionProviderTable.ID + "=" + ExtensionPlayerValueTable.PROVIDER_ID +
WHERE + ExtensionPlayerValueTable.BOOLEAN_VALUE + "=?" +
AND + ExtensionProviderTable.PROVIDED_CONDITION + IS_NOT_NULL;
// Query contents: Set of provided_conditions
return '(' + selectSatisfiedPositiveConditions + " UNION " + selectSatisfiedNegativeConditions + ") q1";
}
private Executable deleteUnsatisfiedValues(String selectSatisfiedConditions) {
// Query contents:
// id | uuid | q1.uuid | condition | q1.provided_condition
// -- | ---- | ------- | --------- | ---------------------
// 1 | ... | ... | A | A Satisfied condition
// 2 | ... | ... | not_B | not_B Satisfied condition
// 3 | ... | ... | NULL | NULL Satisfied condition
// 4 | ... | ... | B | NULL Unsatisfied condition, filtered to these in WHERE clause.
// 5 | ... | ... | not_C | NULL Unsatisfied condition
String selectUnsatisfiedValueIDs = SELECT + playerValueTable + '.' + ExtensionPlayerValueTable.ID +
FROM + providerTable +
INNER_JOIN + playerValueTable + " on " + providerTable + '.' + ExtensionProviderTable.ID + "=" + ExtensionPlayerValueTable.PROVIDER_ID +
LEFT_JOIN + selectSatisfiedConditions + // Left join to preserve values that don't have their condition fulfilled
" on (" + // Join when uuid and plugin_id match and condition for the group provider is satisfied
playerValueTable + '.' + ExtensionPlayerValueTable.USER_UUID +
"=q1." + ExtensionPlayerValueTable.USER_UUID +
AND + ExtensionProviderTable.CONDITION +
"=q1." + ExtensionProviderTable.PROVIDED_CONDITION +
AND + providerTable + '.' + ExtensionProviderTable.PLUGIN_ID +
"=q1." + ExtensionProviderTable.PLUGIN_ID +
')' +
WHERE + "q1." + ExtensionProviderTable.PROVIDED_CONDITION + IS_NULL + // Conditions that were not in the satisfied condition query
AND + ExtensionProviderTable.CONDITION + IS_NOT_NULL; // Ignore values that don't need condition
// Nested query here is required because MySQL limits update statements with nested queries:
// The nested query creates a temporary table that bypasses the same table query-update limit.
// Note: MySQL versions 5.6.7+ might optimize this nested query away leading to an exception.
String sql = DELETE_FROM + playerValueTable +
WHERE + ExtensionPlayerValueTable.ID + " IN (" + SELECT + ExtensionPlayerValueTable.ID + FROM + '(' + selectUnsatisfiedValueIDs + ") as ids)";
return new ExecStatement(sql) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setBoolean(1, true); // Select provided conditions with 'true' value
statement.setBoolean(2, false); // Select negated conditions with 'false' value
}
};
}
private Executable deleteUnsatisfiedTableValues(String selectSatisfiedConditions) {
String selectUnsatisfiedValueIDs = SELECT + ExtensionTableProviderTable.ID +
FROM + tableTable +
LEFT_JOIN + selectSatisfiedConditions + // Left join to preserve values that don't have their condition fulfilled
" on (" + // Join when plugin_id matches and condition for the group provider is satisfied
tableTable + '.' + ExtensionTableProviderTable.CONDITION +
"=q1." + ExtensionProviderTable.PROVIDED_CONDITION +
AND + tableTable + '.' + ExtensionTableProviderTable.PLUGIN_ID +
"=q1." + ExtensionProviderTable.PLUGIN_ID +
')' +
WHERE + "q1." + ExtensionProviderTable.PROVIDED_CONDITION + IS_NULL + // Conditions that were not in the satisfied condition query
AND + ExtensionProviderTable.CONDITION + IS_NOT_NULL; // Ignore values that don't need condition
// Nested query here is required because MySQL limits update statements with nested queries:
// The nested query creates a temporary table that bypasses the same table query-update limit.
// Note: MySQL versions 5.6.7+ might optimize this nested query away leading to an exception.
String deleteValuesSQL = DELETE_FROM + playerTableValueTable +
WHERE + ExtensionPlayerTableValueTable.TABLE_ID + " IN (" + SELECT + ExtensionTableProviderTable.ID + FROM + '(' + selectUnsatisfiedValueIDs + ") as ids)";
return new ExecStatement(deleteValuesSQL) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setBoolean(1, true); // Select provided conditions with 'true' value
statement.setBoolean(2, false); // Select negated conditions with 'false' value
}
};
}
private Executable deleteUnsatisfiedGroupValues(String selectSatisfiedConditions) {
// plan_extensions_player_groups.id is needed for removal of the correct row.
// The id is known if group_id & uuid are known
// -
// Conditions are in plan_extensions_providers
// selectSatisfiedConditions lists 'provided_condition' Strings
String selectUnsatisfiedIDs = SELECT + groupTable + '.' + ID +
FROM + groupTable +
INNER_JOIN + providerTable + " on " + providerTable + '.' + ID + '=' + groupTable + '.' + ExtensionGroupsTable.PROVIDER_ID +
LEFT_JOIN + selectSatisfiedConditions + // Left join to preserve values that don't have their condition fulfilled
" on (" + // Join when uuid and plugin_id match and condition for the group provider is satisfied
groupTable + '.' + P_UUID +
"=q1." + P_UUID +
AND + ExtensionProviderTable.CONDITION +
"=q1." + ExtensionProviderTable.PROVIDED_CONDITION +
AND + providerTable + '.' + ExtensionProviderTable.PLUGIN_ID +
"=q1." + ExtensionProviderTable.PLUGIN_ID +
')' +
WHERE + "q1." + ExtensionProviderTable.PROVIDED_CONDITION + IS_NULL + // Conditions that were not in the satisfied condition query
AND + ExtensionProviderTable.CONDITION + IS_NOT_NULL; // Ignore values that don't need condition
// Nested query here is required because MySQL limits update statements with nested queries:
// The nested query creates a temporary table that bypasses the same table query-update limit.
// Note: MySQL versions 5.6.7+ might optimize this nested query away leading to an exception.
String deleteValuesSQL = DELETE_FROM + groupTable +
WHERE + ID + " IN (" + SELECT + ID + FROM + '(' + selectUnsatisfiedIDs + ") as ids)";
return new ExecStatement(deleteValuesSQL) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setBoolean(1, true); // Select provided conditions with 'true' value
statement.setBoolean(2, false); // Select negated conditions with 'false' value
}
};
}
}

View File

@ -1,150 +0,0 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.extension.implementation.storage.transactions.results;
import com.djrapitops.plan.storage.database.DBType;
import com.djrapitops.plan.storage.database.sql.tables.extension.ExtensionProviderTable;
import com.djrapitops.plan.storage.database.sql.tables.extension.ExtensionServerTableValueTable;
import com.djrapitops.plan.storage.database.sql.tables.extension.ExtensionServerValueTable;
import com.djrapitops.plan.storage.database.sql.tables.extension.ExtensionTableProviderTable;
import com.djrapitops.plan.storage.database.transactions.ExecStatement;
import com.djrapitops.plan.storage.database.transactions.Executable;
import com.djrapitops.plan.storage.database.transactions.ThrowawayTransaction;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import static com.djrapitops.plan.storage.database.sql.building.Sql.*;
/**
* Transaction to remove older results that violate an updated condition value.
* <p>
* How it works:
* - Select all fulfilled conditions for all servers (conditionName when true and not_conditionName when false)
* - Left join with server value and provider tables when plugin_ids match, and when condition matches a condition in the
* query above. (plugin_ids can be linked to servers)
* - Filter the join query for values where the condition did not match any provided condition in the join (Is null)
* - Delete all server values with IDs that are returned by the left join query after filtering
*
* @author AuroraLS3
*/
public class RemoveUnsatisfiedConditionalServerResultsTransaction extends ThrowawayTransaction {
private final String providerTable;
private final String serverValueTable;
private final String serverTableValueTable;
private final String tableTable;
public RemoveUnsatisfiedConditionalServerResultsTransaction() {
providerTable = ExtensionProviderTable.TABLE_NAME;
serverValueTable = ExtensionServerValueTable.TABLE_NAME;
tableTable = ExtensionTableProviderTable.TABLE_NAME;
serverTableValueTable = ExtensionServerTableValueTable.TABLE_NAME;
}
@Override
protected void performOperations() {
String selectSatisfiedConditions = getSatisfiedConditionsSQL();
execute(deleteUnsatisfiedValues(selectSatisfiedConditions));
execute(deleteUnsatisfiedTableValues(selectSatisfiedConditions));
}
private Executable deleteUnsatisfiedValues(String selectSatisfiedConditions) {
// Query contents:
// id | provider_id | q1.provider_id | condition | q1.provided_condition
// -- | ----------- | -------------- | --------- | ---------------------
// 1 | ... | ... | A | A Satisfied condition
// 2 | ... | ... | not_B | not_B Satisfied condition
// 3 | ... | ... | NULL | NULL Satisfied condition
// 4 | ... | ... | B | NULL Unsatisfied condition, filtered to these in WHERE clause.
// 5 | ... | ... | not_C | NULL Unsatisfied condition
String selectUnsatisfiedValueIDs = SELECT + serverValueTable + '.' + ExtensionServerValueTable.ID +
FROM + providerTable +
INNER_JOIN + serverValueTable + " on " + providerTable + '.' + ExtensionProviderTable.ID + "=" + ExtensionServerValueTable.PROVIDER_ID +
LEFT_JOIN + selectSatisfiedConditions + // Left join to preserve values that don't have their condition fulfilled
" on (" +
ExtensionProviderTable.CONDITION + "=q1." + ExtensionProviderTable.PROVIDED_CONDITION +
AND + providerTable + '.' + ExtensionProviderTable.PLUGIN_ID + "=q1." + ExtensionProviderTable.PLUGIN_ID +
')' +
WHERE + "q1." + ExtensionProviderTable.PROVIDED_CONDITION + IS_NULL + // Conditions that were not in the satisfied condition query
AND + ExtensionProviderTable.CONDITION + IS_NOT_NULL; // Ignore values that don't need condition
// Nested query here is required because MySQL limits update statements with nested queries:
// The nested query creates a temporary table that bypasses the same table query-update limit.
// Note: MySQL versions 5.6.7+ might optimize this nested query away leading to an exception.
String sql = DELETE_FROM + serverValueTable +
WHERE + ExtensionServerValueTable.ID + " IN (" + SELECT + ExtensionServerValueTable.ID + FROM + '(' + selectUnsatisfiedValueIDs + ") as ids)";
return new ExecStatement(sql) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setBoolean(1, true); // Select provided conditions with 'true' value
statement.setBoolean(2, false); // Select negated conditions with 'false' value
}
};
}
private String getSatisfiedConditionsSQL() {
String reversedCondition = dbType == DBType.SQLITE ? "'not_' || " + ExtensionProviderTable.PROVIDED_CONDITION : "CONCAT('not_'," + ExtensionProviderTable.PROVIDED_CONDITION + ')';
String selectSatisfiedPositiveConditions = SELECT +
ExtensionProviderTable.PROVIDED_CONDITION + ',' +
ExtensionProviderTable.PLUGIN_ID +
FROM + providerTable +
INNER_JOIN + serverValueTable + " on " + providerTable + '.' + ExtensionProviderTable.ID + "=" + ExtensionServerValueTable.PROVIDER_ID +
WHERE + ExtensionServerValueTable.BOOLEAN_VALUE + "=?" +
AND + ExtensionProviderTable.PROVIDED_CONDITION + IS_NOT_NULL;
String selectSatisfiedNegativeConditions = SELECT +
reversedCondition + " as " + ExtensionProviderTable.PROVIDED_CONDITION + ',' +
ExtensionProviderTable.PLUGIN_ID +
FROM + providerTable +
INNER_JOIN + serverValueTable + " on " + providerTable + '.' + ExtensionProviderTable.ID + "=" + ExtensionServerValueTable.PROVIDER_ID +
WHERE + ExtensionServerValueTable.BOOLEAN_VALUE + "=?" +
AND + ExtensionProviderTable.PROVIDED_CONDITION + IS_NOT_NULL;
// Query contents: Set of provided_conditions
return '(' + selectSatisfiedPositiveConditions + " UNION " + selectSatisfiedNegativeConditions + ") q1";
}
private Executable deleteUnsatisfiedTableValues(String selectSatisfiedConditions) {
String selectUnsatisfiedValueIDs = SELECT + ExtensionTableProviderTable.ID +
FROM + tableTable +
LEFT_JOIN + selectSatisfiedConditions + // Left join to preserve values that don't have their condition fulfilled
" on (" +
tableTable + '.' + ExtensionTableProviderTable.CONDITION +
"=q1." + ExtensionProviderTable.PROVIDED_CONDITION +
AND + tableTable + '.' + ExtensionTableProviderTable.PLUGIN_ID +
"=q1." + ExtensionProviderTable.PLUGIN_ID +
')' +
WHERE + "q1." + ExtensionProviderTable.PROVIDED_CONDITION + IS_NULL + // Conditions that were not in the satisfied condition query
AND + ExtensionProviderTable.CONDITION + IS_NOT_NULL; // Ignore values that don't need condition
// Nested query here is required because MySQL limits update statements with nested queries:
// The nested query creates a temporary table that bypasses the same table query-update limit.
// Note: MySQL versions 5.6.7+ might optimize this nested query away leading to an exception.
String deleteValuesSQL = DELETE_FROM + serverTableValueTable +
WHERE + ExtensionServerTableValueTable.TABLE_ID + " IN (" + SELECT + ExtensionTableProviderTable.ID + FROM + '(' + selectUnsatisfiedValueIDs + ") as ids)";
return new ExecStatement(deleteValuesSQL) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setBoolean(1, true); // Select provided conditions with 'true' value
statement.setBoolean(2, false); // Select negated conditions with 'false' value
}
};
}
}

View File

@ -19,13 +19,20 @@ package com.djrapitops.plan.extension.implementation.storage.transactions.result
import com.djrapitops.plan.extension.implementation.ProviderInformation; import com.djrapitops.plan.extension.implementation.ProviderInformation;
import com.djrapitops.plan.extension.implementation.providers.Parameters; import com.djrapitops.plan.extension.implementation.providers.Parameters;
import com.djrapitops.plan.identification.ServerUUID; import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.storage.database.queries.QueryStatement;
import com.djrapitops.plan.storage.database.sql.building.Sql;
import com.djrapitops.plan.storage.database.sql.tables.extension.ExtensionProviderTable; import com.djrapitops.plan.storage.database.sql.tables.extension.ExtensionProviderTable;
import com.djrapitops.plan.storage.database.transactions.ExecStatement; import com.djrapitops.plan.storage.database.transactions.ExecStatement;
import com.djrapitops.plan.storage.database.transactions.Executable; import com.djrapitops.plan.storage.database.transactions.Executable;
import com.djrapitops.plan.storage.database.transactions.ThrowawayTransaction; import com.djrapitops.plan.storage.database.transactions.ThrowawayTransaction;
import org.intellij.lang.annotations.Language;
import org.jetbrains.annotations.NotNull;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID; import java.util.UUID;
import static com.djrapitops.plan.storage.database.sql.building.Sql.AND; import static com.djrapitops.plan.storage.database.sql.building.Sql.AND;
@ -61,6 +68,13 @@ public class StorePlayerBooleanResultTransaction extends ThrowawayTransaction {
@Override @Override
protected void performOperations() { protected void performOperations() {
execute(storeValue()); execute(storeValue());
commitMidTransaction();
List<Integer> providerIds = selectUnfulfilledProviderIds();
if (!providerIds.isEmpty()) {
execute(deleteUnsatisfiedConditionalResults(providerIds));
execute(deleteUnsatisfiedConditionalGroups(providerIds));
}
execute(deleteUnsatisfiedConditionalTables());
} }
private Executable storeValue() { private Executable storeValue() {
@ -104,4 +118,92 @@ public class StorePlayerBooleanResultTransaction extends ThrowawayTransaction {
} }
}; };
} }
private Executable deleteUnsatisfiedConditionalResults(List<Integer> providerIds) {
@Language("SQL") String deleteUnsatisfiedValues = "DELETE FROM plan_extension_user_values " +
"WHERE uuid=? " +
"AND provider_id IN (" + Sql.nParameters(providerIds.size()) + ")";
return deleteIds(providerIds, deleteUnsatisfiedValues);
}
@NotNull
private ExecStatement deleteIds(List<Integer> providerIds, @Language("SQL") String deleteUnsatisfiedValues) {
return new ExecStatement(deleteUnsatisfiedValues) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setString(1, playerUUID.toString());
for (int i = 0; i < providerIds.size(); i++) {
statement.setInt(i + 2, providerIds.get(i));
}
}
};
}
private Executable deleteUnsatisfiedConditionalGroups(List<Integer> providerIds) {
@Language("SQL") String deleteUnsatisfiedValues = "DELETE FROM plan_extension_groups " +
"WHERE uuid=? " +
"AND provider_id IN (" + Sql.nParameters(providerIds.size()) + ")";
return deleteIds(providerIds, deleteUnsatisfiedValues);
}
private List<Integer> selectUnfulfilledProviderIds() {
// Need to select:
// Provider IDs where condition of this provider is met
@Language("SQL") String selectUnsatisfiedProviderIds = "SELECT unfulfilled.id " +
"FROM plan_extension_providers indb " +
"JOIN plan_extension_providers unfulfilled ON unfulfilled.condition_name=" +
// This gives the unfulfilled condition, eg. if value is true not_condition is unfulfilled.
(value ? Sql.concat(dbType, "'not_'", "indb.provided_condition") : "indb.provided_condition") +
" AND indb.plugin_id=unfulfilled.plugin_id" +
" WHERE indb.id=" + ExtensionProviderTable.STATEMENT_SELECT_PROVIDER_ID +
" AND indb.provided_condition IS NOT NULL";
return extractIds(selectUnsatisfiedProviderIds);
}
private Executable deleteUnsatisfiedConditionalTables() {
List<Integer> tableIds = selectUnfulfilledTableIds();
if (tableIds.isEmpty()) return Executable.empty();
@Language("SQL") String deleteUnsatisfiedValues = "DELETE FROM plan_extension_user_table_values " +
"WHERE uuid=? " +
"AND table_id IN (" + Sql.nParameters(tableIds.size()) + ")";
return deleteIds(tableIds, deleteUnsatisfiedValues);
}
private List<Integer> selectUnfulfilledTableIds() {
// Need to select:
// Provider IDs where condition of this provider is met
@Language("SQL") String selectUnsatisfiedProviderIds = "SELECT unfulfilled.id " +
"FROM plan_extension_providers indb " +
"JOIN plan_extension_tables unfulfilled ON unfulfilled.condition_name=" +
// This gives the unfulfilled condition, eg. if value is true not_condition is unfulfilled.
(value ? Sql.concat(dbType, "'not_'", "indb.provided_condition") : "indb.provided_condition") +
" AND indb.plugin_id=unfulfilled.plugin_id" +
" WHERE indb.id=" + ExtensionProviderTable.STATEMENT_SELECT_PROVIDER_ID +
" AND indb.provided_condition IS NOT NULL";
return extractIds(selectUnsatisfiedProviderIds);
}
private List<Integer> extractIds(@Language("SQL") String selectUnsatisfiedProviderIds) {
return query(new QueryStatement<>(selectUnsatisfiedProviderIds) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
ExtensionProviderTable.set3PluginValuesToStatement(statement, 1, providerName, pluginName, serverUUID);
}
@Override
public List<Integer> processResults(ResultSet set) throws SQLException {
List<Integer> ids = new ArrayList<>();
while (set.next()) {
ids.add(set.getInt(1));
}
return ids;
}
});
}
} }

View File

@ -19,13 +19,19 @@ package com.djrapitops.plan.extension.implementation.storage.transactions.result
import com.djrapitops.plan.extension.implementation.ProviderInformation; import com.djrapitops.plan.extension.implementation.ProviderInformation;
import com.djrapitops.plan.extension.implementation.providers.Parameters; import com.djrapitops.plan.extension.implementation.providers.Parameters;
import com.djrapitops.plan.identification.ServerUUID; import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.storage.database.queries.QueryStatement;
import com.djrapitops.plan.storage.database.sql.building.Sql;
import com.djrapitops.plan.storage.database.sql.tables.extension.ExtensionProviderTable; import com.djrapitops.plan.storage.database.sql.tables.extension.ExtensionProviderTable;
import com.djrapitops.plan.storage.database.transactions.ExecStatement; import com.djrapitops.plan.storage.database.transactions.ExecStatement;
import com.djrapitops.plan.storage.database.transactions.Executable; import com.djrapitops.plan.storage.database.transactions.Executable;
import com.djrapitops.plan.storage.database.transactions.ThrowawayTransaction; import com.djrapitops.plan.storage.database.transactions.ThrowawayTransaction;
import org.intellij.lang.annotations.Language;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import static com.djrapitops.plan.storage.database.sql.building.Sql.WHERE; import static com.djrapitops.plan.storage.database.sql.building.Sql.WHERE;
import static com.djrapitops.plan.storage.database.sql.tables.extension.ExtensionServerValueTable.*; import static com.djrapitops.plan.storage.database.sql.tables.extension.ExtensionServerValueTable.*;
@ -61,6 +67,9 @@ public class StoreServerBooleanResultTransaction extends ThrowawayTransaction {
@Override @Override
protected void performOperations() { protected void performOperations() {
execute(storeValue()); execute(storeValue());
commitMidTransaction();
execute(deleteUnsatisfiedConditionalResults());
execute(deleteUnsatisfiedConditionalTables());
} }
private Executable storeValue() { private Executable storeValue() {
@ -100,4 +109,86 @@ public class StoreServerBooleanResultTransaction extends ThrowawayTransaction {
} }
}; };
} }
private Executable deleteUnsatisfiedConditionalResults() {
List<Integer> providerIds = selectUnfulfilledProviderIds();
if (providerIds.isEmpty()) return Executable.empty();
@Language("SQL") String deleteUnsatisfiedValues = "DELETE FROM plan_extension_server_values " +
"WHERE provider_id IN (" + Sql.nParameters(providerIds.size()) + ")";
return new ExecStatement(deleteUnsatisfiedValues) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
for (int i = 0; i < providerIds.size(); i++) {
statement.setInt(i + 1, providerIds.get(i));
}
}
};
}
private List<Integer> selectUnfulfilledProviderIds() {
// Need to select:
// Provider IDs where condition of this provider is met
@Language("SQL") String selectUnsatisfiedProviderIds = "SELECT unfulfilled.id " +
"FROM plan_extension_providers indb " +
"JOIN plan_extension_providers unfulfilled ON unfulfilled.condition_name=" +
// This gives the unfulfilled condition, eg. if value is true not_condition is unfulfilled.
(value ? Sql.concat(dbType, "'not_'", "indb.provided_condition") : "indb.provided_condition") +
" AND indb.plugin_id=unfulfilled.plugin_id" +
" WHERE indb.id=" + ExtensionProviderTable.STATEMENT_SELECT_PROVIDER_ID +
" AND indb.provided_condition IS NOT NULL";
return extractIds(selectUnsatisfiedProviderIds);
}
private Executable deleteUnsatisfiedConditionalTables() {
List<Integer> tableIds = selectUnfulfilledTableIds();
if (tableIds.isEmpty()) return Executable.empty();
@Language("SQL") String deleteUnsatisfiedValues = "DELETE FROM plan_extension_server_table_values " +
"WHERE table_id IN (" + Sql.nParameters(tableIds.size()) + ")";
return new ExecStatement(deleteUnsatisfiedValues) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
for (int i = 0; i < tableIds.size(); i++) {
statement.setInt(i + 1, tableIds.get(i));
}
}
};
}
private List<Integer> selectUnfulfilledTableIds() {
// Need to select:
// Provider IDs where condition of this provider is met
@Language("SQL") String selectUnsatisfiedProviderIds = "SELECT unfulfilled.id " +
"FROM plan_extension_providers indb " +
"JOIN plan_extension_tables unfulfilled ON unfulfilled.condition_name=" +
// This gives the unfulfilled condition, eg. if value is true not_condition is unfulfilled.
(value ? Sql.concat(dbType, "'not_'", "indb.provided_condition") : "indb.provided_condition") +
" AND indb.plugin_id=unfulfilled.plugin_id" +
" WHERE indb.id=" + ExtensionProviderTable.STATEMENT_SELECT_PROVIDER_ID +
" AND indb.provided_condition IS NOT NULL";
return extractIds(selectUnsatisfiedProviderIds);
}
private List<Integer> extractIds(@Language("SQL") String selectUnsatisfiedProviderIds) {
return query(new QueryStatement<>(selectUnsatisfiedProviderIds) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
ExtensionProviderTable.set3PluginValuesToStatement(statement, 1, providerName, pluginName, serverUUID);
}
@Override
public List<Integer> processResults(ResultSet set) throws SQLException {
List<Integer> ids = new ArrayList<>();
while (set.next()) {
ids.add(set.getInt(1));
}
return ids;
}
});
}
} }

View File

@ -0,0 +1,94 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.gathering;
import com.djrapitops.plan.settings.config.PlanConfig;
import com.djrapitops.plan.settings.config.paths.DataGatheringSettings;
import com.djrapitops.plan.utilities.dev.Untrusted;
import org.apache.commons.lang3.StringUtils;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
/**
* Utility for validating and sanitizing join addresses.
*
* @author AuroraLS3
*/
@Singleton
public class JoinAddressValidator {
private final PlanConfig config;
private List<String> filteredAddresses;
@Inject
public JoinAddressValidator(PlanConfig config) {
/* Dagger injection constructor */
this.config = config;
}
private void prepareFilteredAddresses() {
if (filteredAddresses == null) {
filteredAddresses = config.get(DataGatheringSettings.FILTER_JOIN_ADDRESSES);
if (filteredAddresses == null) filteredAddresses = new ArrayList<>();
}
}
@Untrusted
public String sanitize(@Untrusted String address) {
if (address == null || config.isFalse(DataGatheringSettings.JOIN_ADDRESSES)) return "";
if (!address.isEmpty()) {
// Remove port
if (address.contains(":")) {
address = address.substring(0, address.lastIndexOf(':'));
}
// Remove data added by Bungeecord/Velocity
if (address.contains("\u0000")) {
address = address.substring(0, address.indexOf('\u0000'));
}
// Remove data added by Forge Mod Loader
if (address.contains("fml")) {
address = address.substring(0, address.lastIndexOf("fml"));
}
if (config.isFalse(DataGatheringSettings.PRESERVE_JOIN_ADDRESS_CASE)) {
address = StringUtils.lowerCase(address);
}
prepareFilteredAddresses();
if (filteredAddresses.contains(address)) {
address = "";
}
}
return address;
}
public boolean isValid(@Untrusted String address) {
if (address.isEmpty()) return false;
if (config.isTrue(DataGatheringSettings.PRESERVE_INVALID_JOIN_ADDRESS)) return true;
try {
URI uri = new URI(address);
String path = uri.getPath();
return path != null && path.indexOf('.') != -1;
} catch (URISyntaxException uriSyntaxException) {
return false;
}
}
}

View File

@ -48,7 +48,6 @@ public abstract class ServerShutdownSave {
private final ErrorLogger errorLogger; private final ErrorLogger errorLogger;
private boolean shuttingDown = false; private boolean shuttingDown = false;
private boolean startedDatabase = false;
protected ServerShutdownSave( protected ServerShutdownSave(
Locale locale, Locale locale,
@ -90,7 +89,7 @@ public abstract class ServerShutdownSave {
private Optional<Future<?>> attemptSave(Collection<ActiveSession> activeSessions) { private Optional<Future<?>> attemptSave(Collection<ActiveSession> activeSessions) {
try { try {
return Optional.of(saveActiveSessions(finishSessions(activeSessions, System.currentTimeMillis()))); return saveActiveSessions(finishSessions(activeSessions, System.currentTimeMillis()));
} catch (DBInitException e) { } catch (DBInitException e) {
errorLogger.error(e, ErrorContext.builder() errorLogger.error(e, ErrorContext.builder()
.whatToDo("Find the sessions in the error file and save them manually or ignore. Report & delete the error file after.") .whatToDo("Find the sessions in the error file and save them manually or ignore. Report & delete the error file after.")
@ -101,20 +100,19 @@ public abstract class ServerShutdownSave {
} catch (IllegalStateException ignored) { } catch (IllegalStateException ignored) {
/* Database is not initialized */ /* Database is not initialized */
return Optional.empty(); return Optional.empty();
} finally {
closeDatabase(dbSystem.getDatabase());
} }
} }
private Future<?> saveActiveSessions(Collection<FinishedSession> finishedSessions) { private Optional<Future<?>> saveActiveSessions(Collection<FinishedSession> finishedSessions) {
Database database = dbSystem.getDatabase(); Database database = dbSystem.getDatabase();
if (database.getState() == Database.State.CLOSED) { if (database.getState() == Database.State.CLOSED) {
// Ensure that database is not closed when performing the transaction. // Don't attempt to save if database is closed, session storage will be handled by
startedDatabase = true; // ShutdownDataPreservation instead.
database.init(); // Previously database reboot was attempted, but this could lead to server hang.
return Optional.empty();
} }
return saveSessions(finishedSessions, database); return Optional.of(saveSessions(finishedSessions, database));
} }
Collection<FinishedSession> finishSessions(Collection<ActiveSession> activeSessions, long now) { Collection<FinishedSession> finishSessions(Collection<ActiveSession> activeSessions, long now) {
@ -127,10 +125,4 @@ public abstract class ServerShutdownSave {
private Future<?> saveSessions(Collection<FinishedSession> finishedSessions, Database database) { private Future<?> saveSessions(Collection<FinishedSession> finishedSessions, Database database) {
return database.executeTransaction(new ServerShutdownTransaction(finishedSessions)); return database.executeTransaction(new ServerShutdownTransaction(finishedSessions));
} }
private void closeDatabase(Database database) {
if (startedDatabase) {
database.close();
}
}
} }

View File

@ -16,6 +16,8 @@
*/ */
package com.djrapitops.plan.gathering.domain; package com.djrapitops.plan.gathering.domain;
import com.djrapitops.plan.utilities.dev.Untrusted;
import java.net.InetAddress; import java.net.InetAddress;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
@ -24,8 +26,10 @@ public interface PlatformPlayerData {
UUID getUUID(); UUID getUUID();
@Untrusted
String getName(); String getName();
@Untrusted
default Optional<String> getDisplayName() { default Optional<String> getDisplayName() {
return Optional.empty(); return Optional.empty();
} }
@ -38,6 +42,7 @@ public interface PlatformPlayerData {
return Optional.empty(); return Optional.empty();
} }
@Untrusted
default Optional<String> getJoinAddress() { default Optional<String> getJoinAddress() {
return Optional.empty(); return Optional.empty();
} }

View File

@ -64,6 +64,13 @@ public class PlayerJoin {
return time; return time;
} }
/**
* Get address used to join the server.
*
* @return Join address of the player.
* @deprecated {@link com.djrapitops.plan.gathering.JoinAddressValidator} should be used when looking at join address.
*/
@Deprecated(since = "2024-04-27")
public String getJoinAddress() { public String getJoinAddress() {
return player.getJoinAddress().orElse(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP); return player.getJoinAddress().orElse(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP);
} }

View File

@ -22,6 +22,7 @@ import com.djrapitops.plan.delivery.domain.ServerName;
import com.djrapitops.plan.delivery.export.Exporter; import com.djrapitops.plan.delivery.export.Exporter;
import com.djrapitops.plan.extension.CallEvents; import com.djrapitops.plan.extension.CallEvents;
import com.djrapitops.plan.extension.ExtensionSvc; import com.djrapitops.plan.extension.ExtensionSvc;
import com.djrapitops.plan.gathering.JoinAddressValidator;
import com.djrapitops.plan.gathering.cache.NicknameCache; import com.djrapitops.plan.gathering.cache.NicknameCache;
import com.djrapitops.plan.gathering.cache.SessionCache; import com.djrapitops.plan.gathering.cache.SessionCache;
import com.djrapitops.plan.gathering.domain.ActiveSession; import com.djrapitops.plan.gathering.domain.ActiveSession;
@ -38,7 +39,6 @@ import com.djrapitops.plan.storage.database.DBSystem;
import com.djrapitops.plan.storage.database.sql.tables.JoinAddressTable; import com.djrapitops.plan.storage.database.sql.tables.JoinAddressTable;
import com.djrapitops.plan.storage.database.transactions.Transaction; import com.djrapitops.plan.storage.database.transactions.Transaction;
import com.djrapitops.plan.storage.database.transactions.events.*; import com.djrapitops.plan.storage.database.transactions.events.*;
import org.apache.commons.lang3.StringUtils;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
@ -52,6 +52,7 @@ public class PlayerJoinEventConsumer {
private final PlanConfig config; private final PlanConfig config;
private final DBSystem dbSystem; private final DBSystem dbSystem;
private final JoinAddressValidator joinAddressValidator;
private final GeolocationCache geolocationCache; private final GeolocationCache geolocationCache;
private final SessionCache sessionCache; private final SessionCache sessionCache;
private final NicknameCache nicknameCache; private final NicknameCache nicknameCache;
@ -63,7 +64,7 @@ public class PlayerJoinEventConsumer {
public PlayerJoinEventConsumer( public PlayerJoinEventConsumer(
Processing processing, Processing processing,
PlanConfig config, PlanConfig config,
DBSystem dbSystem, DBSystem dbSystem, JoinAddressValidator joinAddressValidator,
GeolocationCache geolocationCache, GeolocationCache geolocationCache,
SessionCache sessionCache, SessionCache sessionCache,
NicknameCache nicknameCache, NicknameCache nicknameCache,
@ -73,6 +74,7 @@ public class PlayerJoinEventConsumer {
this.processing = processing; this.processing = processing;
this.config = config; this.config = config;
this.dbSystem = dbSystem; this.dbSystem = dbSystem;
this.joinAddressValidator = joinAddressValidator;
this.geolocationCache = geolocationCache; this.geolocationCache = geolocationCache;
this.sessionCache = sessionCache; this.sessionCache = sessionCache;
this.nicknameCache = nicknameCache; this.nicknameCache = nicknameCache;
@ -110,7 +112,8 @@ public class PlayerJoinEventConsumer {
private void storeJoinAddress(PlayerJoin join) { private void storeJoinAddress(PlayerJoin join) {
join.getPlayer().getJoinAddress() join.getPlayer().getJoinAddress()
.map(joinAddress -> config.isTrue(DataGatheringSettings.PRESERVE_JOIN_ADDRESS_CASE) ? joinAddress : StringUtils.lowerCase(joinAddress)) .map(joinAddressValidator::sanitize)
.filter(joinAddressValidator::isValid)
.map(StoreJoinAddressTransaction::new) .map(StoreJoinAddressTransaction::new)
.ifPresent(dbSystem.getDatabase()::executeTransaction); .ifPresent(dbSystem.getDatabase()::executeTransaction);
} }
@ -141,7 +144,10 @@ public class PlayerJoinEventConsumer {
private CompletableFuture<?> storeGamePlayer(PlayerJoin join) { private CompletableFuture<?> storeGamePlayer(PlayerJoin join) {
long registerDate = getRegisterDate(join); long registerDate = getRegisterDate(join);
String joinAddress = join.getPlayer().getJoinAddress().orElse(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP); String joinAddress = join.getPlayer().getJoinAddress()
.map(joinAddressValidator::sanitize)
.filter(joinAddressValidator::isValid)
.orElse(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP);
Transaction transaction = new StoreServerPlayerTransaction( Transaction transaction = new StoreServerPlayerTransaction(
join.getPlayerUUID(), registerDate, join.getPlayer().getName(), join.getServerUUID(), joinAddress join.getPlayerUUID(), registerDate, join.getPlayer().getName(), join.getServerUUID(), joinAddress
); );
@ -171,12 +177,16 @@ public class PlayerJoinEventConsumer {
} }
private ActiveSession mapToActiveSession(PlayerJoin join) { private ActiveSession mapToActiveSession(PlayerJoin join) {
String joinAddress = join.getPlayer().getJoinAddress()
.map(joinAddressValidator::sanitize)
.filter(joinAddressValidator::isValid)
.orElse(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP);
ActiveSession session = new ActiveSession(join.getPlayerUUID(), join.getServerUUID(), join.getTime(), ActiveSession session = new ActiveSession(join.getPlayerUUID(), join.getServerUUID(), join.getTime(),
join.getPlayer().getCurrentWorld().orElse(null), join.getPlayer().getCurrentWorld().orElse(null),
join.getPlayer().getCurrentGameMode().orElse(null)); join.getPlayer().getCurrentGameMode().orElse(null));
session.getExtraData().put(PlayerName.class, new PlayerName(join.getPlayer().getName())); session.getExtraData().put(PlayerName.class, new PlayerName(join.getPlayer().getName()));
session.getExtraData().put(ServerName.class, new ServerName(join.getServer().isProxy() ? join.getServer().getName() : "Proxy Server")); session.getExtraData().put(ServerName.class, new ServerName(join.getServer().isProxy() ? join.getServer().getName() : "Proxy Server"));
session.getExtraData().put(JoinAddress.class, new JoinAddress(config.isTrue(DataGatheringSettings.PRESERVE_JOIN_ADDRESS_CASE) ? join.getJoinAddress() : StringUtils.lowerCase(join.getJoinAddress()))); session.getExtraData().put(JoinAddress.class, new JoinAddress(joinAddress));
return session; return session;
} }

View File

@ -20,15 +20,13 @@ import com.djrapitops.plan.exceptions.PreparationException;
import com.djrapitops.plan.settings.config.PlanConfig; import com.djrapitops.plan.settings.config.PlanConfig;
import com.djrapitops.plan.settings.config.paths.DataGatheringSettings; import com.djrapitops.plan.settings.config.paths.DataGatheringSettings;
import com.djrapitops.plan.storage.file.PlanFiles; import com.djrapitops.plan.storage.file.PlanFiles;
import com.djrapitops.plan.utilities.Base64Util;
import com.maxmind.geoip2.DatabaseReader; import com.maxmind.geoip2.DatabaseReader;
import com.maxmind.geoip2.exception.GeoIp2Exception; import com.maxmind.geoip2.exception.GeoIp2Exception;
import com.maxmind.geoip2.model.CountryResponse; import com.maxmind.geoip2.model.CountryResponse;
import com.maxmind.geoip2.record.Country; import com.maxmind.geoip2.record.Country;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry; import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
import org.apache.commons.compress.utils.IOUtils; import org.apache.commons.io.IOUtils;
import org.jasypt.encryption.pbe.StandardPBEStringEncryptor;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
@ -38,10 +36,10 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.URL; import java.net.URL;
import java.net.URLConnection;
import java.nio.file.Files; import java.nio.file.Files;
import java.util.*; import java.util.*;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.zip.GZIPInputStream; import java.util.zip.GZIPInputStream;
/** /**
@ -83,62 +81,45 @@ public class GeoLite2Geolocator implements Geolocator {
Files.delete(geolocationDB.toPath()); // Delete old data according to restriction 3. in EULA Files.delete(geolocationDB.toPath()); // Delete old data according to restriction 3. in EULA
} }
} }
downloadDatabase(); downloadDatabase();
// Delete old Geolocation database file if it still exists (on success to avoid a no-file situation) // Delete old Geolocation database file if it still exists (on success to avoid a no-file situation)
Files.deleteIfExists(files.getFileFromPluginFolder("GeoIP.dat").toPath()); Files.deleteIfExists(files.getFileFromPluginFolder("GeoIP.dat").toPath());
} }
private static String a(String c, String d) {
var o = new StandardPBEStringEncryptor();
g(c, q(o));
return o.decrypt(d);
}
private static void g(String h, Consumer<String> b) {
b.accept(l(h));
}
private static Consumer<String> q(StandardPBEStringEncryptor t) {
return t::setPassword;
}
private static String l(String f) {
return Base64Util.decode(f);
}
private void downloadDatabase() throws IOException { private void downloadDatabase() throws IOException {
// Avoid Socket leak with the parameters in case download url has proxy // Avoid Socket leak with the parameters in case download url has proxy
// https://AuroraLS3.github.io/mishaps/java_socket_leak_incident
Properties properties = System.getProperties(); Properties properties = System.getProperties();
properties.setProperty("sun.net.client.defaultConnectTimeout", Long.toString(TimeUnit.MINUTES.toMillis(1L))); properties.setProperty("sun.net.client.defaultConnectTimeout", Long.toString(TimeUnit.MINUTES.toMillis(1L)));
properties.setProperty("sun.net.client.defaultReadTimeout", Long.toString(TimeUnit.MINUTES.toMillis(1L))); properties.setProperty("sun.net.client.defaultReadTimeout", Long.toString(TimeUnit.MINUTES.toMillis(1L)));
properties.setProperty("sun.net.http.retryPost", Boolean.toString(false)); properties.setProperty("sun.net.http.retryPost", Boolean.toString(false));
String key = getKey(); String downloadURL = config.get(DataGatheringSettings.GEOLOCATION_DOWNLOAD_URL);
String downloadFrom = "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-Country&license_key=" + key + "&suffix=tar.gz"; URL downloadSite = new URL(downloadURL);
URL downloadSite = new URL(downloadFrom); if (downloadURL.startsWith("https://download.maxmind.com/app/geoip_download")) {
try ( try (
InputStream in = downloadSite.openStream(); InputStream in = downloadSite.openStream();
GZIPInputStream gzipIn = new GZIPInputStream(in); GZIPInputStream gzipIn = new GZIPInputStream(in);
TarArchiveInputStream tarIn = new TarArchiveInputStream(gzipIn); TarArchiveInputStream tarIn = new TarArchiveInputStream(gzipIn);
FileOutputStream fos = new FileOutputStream(geolocationDB.getAbsoluteFile()) FileOutputStream fos = new FileOutputStream(geolocationDB.getAbsoluteFile())
) { ) {
findAndCopyFromTar(tarIn, fos); findAndCopyFromTar(tarIn, fos);
}
} else {
URLConnection connection = downloadSite.openConnection();
connection.setRequestProperty("X-PLAN-GEODB-TOKEN", "68342d1f-5fc9-4853-bd1e-ba88c466b3a6");
try (
InputStream in = connection.getInputStream();
FileOutputStream fos = new FileOutputStream(geolocationDB.getAbsoluteFile())
) {
IOUtils.copy(in, fos);
}
} }
} }
private String getKey() throws IOException {
String y = "bGljZW5z";
String u = new String(files.getResourceFromJar(y + "ZV9wYXNz.txt").asBytes());
String h = new String(files.getResourceFromJar(y + "ZV9rZXlz.txt").asBytes());
return a(u, h);
}
private void findAndCopyFromTar(TarArchiveInputStream tarIn, FileOutputStream fos) throws IOException { private void findAndCopyFromTar(TarArchiveInputStream tarIn, FileOutputStream fos) throws IOException {
// Breadth first search // Breadth first search
Queue<TarArchiveEntry> entries = new ArrayDeque<>(); Queue<TarArchiveEntry> entries = new ArrayDeque<>();
entries.add(tarIn.getNextTarEntry()); entries.add(tarIn.getNextEntry());
while (!entries.isEmpty()) { while (!entries.isEmpty()) {
TarArchiveEntry entry = entries.poll(); TarArchiveEntry entry = entries.poll();
if (entry.isDirectory()) { if (entry.isDirectory()) {
@ -151,7 +132,7 @@ public class GeoLite2Geolocator implements Geolocator {
break; // Found it break; // Found it
} }
TarArchiveEntry next = tarIn.getNextTarEntry(); TarArchiveEntry next = tarIn.getNextEntry();
if (next != null) entries.add(next); if (next != null) entries.add(next);
} }
} }

View File

@ -57,7 +57,7 @@ public class InstalledPluginGatheringTask extends TaskSystem.Task {
@Override @Override
public void register(RunnableFactory runnableFactory) { public void register(RunnableFactory runnableFactory) {
runnableFactory.create(this) runnableFactory.create(this)
.runTaskLater(20, TimeUnit.SECONDS); .runTaskLaterAsynchronously(20, TimeUnit.SECONDS);
} }
@Override @Override

View File

@ -27,6 +27,7 @@ import com.djrapitops.plan.gathering.domain.ActiveSession;
import com.djrapitops.plan.gathering.domain.FinishedSession; import com.djrapitops.plan.gathering.domain.FinishedSession;
import com.djrapitops.plan.gathering.domain.GeoInfo; import com.djrapitops.plan.gathering.domain.GeoInfo;
import com.djrapitops.plan.gathering.domain.PlayerKill; import com.djrapitops.plan.gathering.domain.PlayerKill;
import com.djrapitops.plan.gathering.domain.event.JoinAddress;
import com.djrapitops.plan.identification.Server; import com.djrapitops.plan.identification.Server;
import com.djrapitops.plan.identification.ServerInfo; import com.djrapitops.plan.identification.ServerInfo;
import com.djrapitops.plan.settings.config.PlanConfig; import com.djrapitops.plan.settings.config.PlanConfig;
@ -157,6 +158,14 @@ public class PlayerPlaceHolders implements Placeholders {
.orElse(locale.getString(GenericLang.UNKNOWN)) .orElse(locale.getString(GenericLang.UNKNOWN))
); );
placeholders.register("player_join_address",
player -> SessionsMutator.forContainer(player)
.latestSession()
.flatMap(session -> session.getExtraData(JoinAddress.class))
.map(JoinAddress::getAddress)
.orElse(locale.getString(GenericLang.UNKNOWN))
);
registerPlaytimePlaceholders(placeholders, time); registerPlaytimePlaceholders(placeholders, time);
registerSessionLengethPlaceholders(placeholders, time); registerSessionLengethPlaceholders(placeholders, time);

View File

@ -19,12 +19,17 @@ package com.djrapitops.plan.placeholder;
import com.djrapitops.plan.commands.use.Arguments; import com.djrapitops.plan.commands.use.Arguments;
import com.djrapitops.plan.delivery.formatting.Formatter; import com.djrapitops.plan.delivery.formatting.Formatter;
import com.djrapitops.plan.delivery.formatting.Formatters; import com.djrapitops.plan.delivery.formatting.Formatters;
import com.djrapitops.plan.gathering.ServerUptimeCalculator;
import com.djrapitops.plan.identification.Server; import com.djrapitops.plan.identification.Server;
import com.djrapitops.plan.identification.ServerInfo; import com.djrapitops.plan.identification.ServerInfo;
import com.djrapitops.plan.identification.ServerUUID; import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.settings.config.PlanConfig;
import com.djrapitops.plan.settings.config.paths.TimeSettings;
import com.djrapitops.plan.storage.database.DBSystem; import com.djrapitops.plan.storage.database.DBSystem;
import com.djrapitops.plan.storage.database.Database; import com.djrapitops.plan.storage.database.Database;
import com.djrapitops.plan.storage.database.queries.Query; import com.djrapitops.plan.storage.database.queries.Query;
import com.djrapitops.plan.storage.database.queries.analysis.ActivityIndexQueries;
import com.djrapitops.plan.storage.database.queries.analysis.NetworkActivityIndexQueries;
import com.djrapitops.plan.storage.database.queries.analysis.PlayerCountQueries; import com.djrapitops.plan.storage.database.queries.analysis.PlayerCountQueries;
import com.djrapitops.plan.storage.database.queries.analysis.TopListQueries; import com.djrapitops.plan.storage.database.queries.analysis.TopListQueries;
import com.djrapitops.plan.storage.database.queries.objects.ServerQueries; import com.djrapitops.plan.storage.database.queries.objects.ServerQueries;
@ -38,6 +43,7 @@ import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import static com.djrapitops.plan.utilities.MiscUtils.*; import static com.djrapitops.plan.utilities.MiscUtils.*;
@ -49,19 +55,24 @@ import static com.djrapitops.plan.utilities.MiscUtils.*;
@Singleton @Singleton
public class ServerPlaceHolders implements Placeholders { public class ServerPlaceHolders implements Placeholders {
private final PlanConfig config;
private final DBSystem dbSystem; private final DBSystem dbSystem;
private final ServerInfo serverInfo; private final ServerInfo serverInfo;
private final Formatters formatters; private final Formatters formatters;
private final ServerUptimeCalculator serverUptimeCalculator;
@Inject @Inject
public ServerPlaceHolders( public ServerPlaceHolders(
PlanConfig config,
DBSystem dbSystem, DBSystem dbSystem,
ServerInfo serverInfo, ServerInfo serverInfo,
Formatters formatters Formatters formatters, ServerUptimeCalculator serverUptimeCalculator
) { ) {
this.config = config;
this.dbSystem = dbSystem; this.dbSystem = dbSystem;
this.serverInfo = serverInfo; this.serverInfo = serverInfo;
this.formatters = formatters; this.formatters = formatters;
this.serverUptimeCalculator = serverUptimeCalculator;
} }
@Override @Override
@ -196,9 +207,19 @@ public class ServerPlaceHolders implements Placeholders {
placeholders.registerStatic("server_name", placeholders.registerStatic("server_name",
() -> serverInfo.getServer().getName()); () -> serverInfo.getServer().getName());
placeholders.registerStatic("server_uptime",
parameters -> serverUptimeCalculator.getServerUptimeMillis(getServerUUID(parameters))
.map(String::valueOf)
.orElse("-"));
placeholders.registerStatic("server_uuid", placeholders.registerStatic("server_uuid",
serverInfo::getServerUUID); serverInfo::getServerUUID);
placeholders.registerStatic("regular_players",
parameters -> database.query(ActivityIndexQueries.fetchRegularPlayerCount(System.currentTimeMillis(), getServerUUID(parameters), config.get(TimeSettings.ACTIVE_PLAY_THRESHOLD))));
placeholders.registerStatic("network_regular_players",
() -> database.query(NetworkActivityIndexQueries.fetchRegularPlayerCount(System.currentTimeMillis(), config.get(TimeSettings.ACTIVE_PLAY_THRESHOLD))));
registerDynamicCategoryPlaceholders(placeholders, database); registerDynamicCategoryPlaceholders(placeholders, database);
} }
@ -214,20 +235,22 @@ public class ServerPlaceHolders implements Placeholders {
private void registerDynamicCategoryPlaceholders(PlanPlaceholders placeholders, Database database) { private void registerDynamicCategoryPlaceholders(PlanPlaceholders placeholders, Database database) {
List<TopCategoryQuery<Long>> queries = new ArrayList<>(); List<TopCategoryQuery<Long>> queries = new ArrayList<>();
queries.addAll(createCategoryQueriesForAllTimespans("playtime", (index, timespan, parameters) -> TopListQueries.fetchNthTop10PlaytimePlayerOn(getServerUUID(parameters), index, System.currentTimeMillis() - timespan, System.currentTimeMillis()))); queries.addAll(createCategoryQueriesForAllTimespans("playtime", (index, timespan, parameters) -> TopListQueries.fetchNthTop10PlaytimePlayerOn(getServerUUID(parameters), index, System.currentTimeMillis() - timespan, System.currentTimeMillis())));
queries.addAll(createCategoryQueriesForAllTimespans("network_playtime", (index, timespan, parameters) -> TopListQueries.fetchNthTop10PlaytimePlayerOn(null, index, System.currentTimeMillis() - timespan, System.currentTimeMillis())));
queries.addAll(createCategoryQueriesForAllTimespans("active_playtime", (index, timespan, parameters) -> TopListQueries.fetchNthTop10ActivePlaytimePlayerOn(getServerUUID(parameters), index, System.currentTimeMillis() - timespan, System.currentTimeMillis()))); queries.addAll(createCategoryQueriesForAllTimespans("active_playtime", (index, timespan, parameters) -> TopListQueries.fetchNthTop10ActivePlaytimePlayerOn(getServerUUID(parameters), index, System.currentTimeMillis() - timespan, System.currentTimeMillis())));
queries.addAll(createCategoryQueriesForAllTimespans("network_active_playtime", (index, timespan, parameters) -> TopListQueries.fetchNthTop10ActivePlaytimePlayerOn(null, index, System.currentTimeMillis() - timespan, System.currentTimeMillis())));
queries.addAll(createCategoryQueriesForAllTimespans("player_kills", (index, timespan, parameters) -> TopListQueries.fetchNthTop10PlayerKillCountOn(getServerUUID(parameters), index, System.currentTimeMillis() - timespan, System.currentTimeMillis()))); queries.addAll(createCategoryQueriesForAllTimespans("player_kills", (index, timespan, parameters) -> TopListQueries.fetchNthTop10PlayerKillCountOn(getServerUUID(parameters), index, System.currentTimeMillis() - timespan, System.currentTimeMillis())));
for (int i = 0; i < 10; i++) { for (int i = 0; i < 10; i++) {
for (TopCategoryQuery<Long> query : queries) { for (TopCategoryQuery<Long> query : queries) {
final int nth = i; final int nth = i;
placeholders.registerStatic(String.format("top_%s_%s_%s", query.getCategory(), query.getTimeSpan(), nth), placeholders.registerStatic(String.format("top_%s_%s_%s", query.getCategory(), query.getTimeSpan(), nth + 1),
parameters -> database.query(query.getQuery(nth, parameters)) parameters -> database.query(query.getQuery(nth, parameters))
.map(TopListQueries.TopListEntry::getPlayerName) .map(TopListQueries.TopListEntry::getPlayerName)
.orElse("-")); .orElse("-"));
placeholders.registerStatic(String.format("top_%s_%s_%s_value", query.getCategory(), query.getTimeSpan(), nth), placeholders.registerStatic(String.format("top_%s_%s_%s_value", query.getCategory(), query.getTimeSpan(), nth + 1),
parameters -> database.query(query.getQuery(nth, parameters)) parameters -> database.query(query.getQuery(nth, parameters))
.map(TopListQueries.TopListEntry::getValue) .map(TopListQueries.TopListEntry::getValue)
.map(formatters.timeAmount()) .map(query.getCategory().equals("player_kills") ? Function.identity() : formatters.timeAmount())
.orElse("-")); .orElse("-"));
} }
} }

View File

@ -52,6 +52,11 @@ public class SessionPlaceHolders implements Placeholders {
private final ServerInfo serverInfo; private final ServerInfo serverInfo;
private final Formatters formatters; private final Formatters formatters;
private Formatter<Long> timeAmount;
private Formatter<DateHolder> year;
private Formatter<Double> decimals;
private Database database;
@Inject @Inject
public SessionPlaceHolders( public SessionPlaceHolders(
PlanConfig config, PlanConfig config,
@ -71,67 +76,149 @@ public class SessionPlaceHolders implements Placeholders {
return timeAmount.apply(sessionCount != 0 ? playtime / sessionCount : playtime); return timeAmount.apply(sessionCount != 0 ? playtime / sessionCount : playtime);
} }
private static String getPlaytime(Database database, long after, long before, Formatter<Long> timeAmount) {
Long playtime = database.query(SessionQueries.playtime(after, before));
Long sessionCount = database.query(SessionQueries.sessionCount(after, before));
return timeAmount.apply(sessionCount != 0 ? playtime / sessionCount : playtime);
}
@Override @Override
public void register( public void register(
PlanPlaceholders placeholders PlanPlaceholders placeholders
) { ) {
int tzOffsetMs = config.getTimeZone().getOffset(System.currentTimeMillis()); int tzOffsetMs = config.getTimeZone().getOffset(System.currentTimeMillis());
Formatter<Long> timeAmount = formatters.timeAmount(); timeAmount = formatters.timeAmount();
Formatter<DateHolder> year = formatters.year(); year = formatters.year();
Formatter<Double> decimals = formatters.decimals(); decimals = formatters.decimals();
Database database = dbSystem.getDatabase(); database = dbSystem.getDatabase();
placeholders.registerStatic("sessions_play_time_total", registerServerPlaytime(placeholders);
parameters -> timeAmount.apply(database.query(SessionQueries.playtime(0L, now(), getServerUUID(parameters))))); registerNetworkPlaytime(placeholders);
placeholders.registerStatic("sessions_play_time_total_raw", registerServerActivePlaytime(placeholders);
parameters -> database.query(SessionQueries.playtime(0L, now(), getServerUUID(parameters)))); registerNetworkActivePlaytime(placeholders);
placeholders.registerStatic("sessions_play_time_day", registerServerAfkTime(placeholders);
parameters -> timeAmount.apply(database.query(SessionQueries.playtime(dayAgo(), now(), getServerUUID(parameters))))); registerNetworkAfkTime(placeholders);
placeholders.registerStatic("sessions_play_time_day_raw",
parameters -> database.query(SessionQueries.playtime(dayAgo(), now(), getServerUUID(parameters))));
placeholders.registerStatic("sessions_play_time_week",
parameters -> timeAmount.apply(database.query(SessionQueries.playtime(weekAgo(), now(), getServerUUID(parameters)))));
placeholders.registerStatic("sessions_play_time_week_raw",
parameters -> database.query(SessionQueries.playtime(weekAgo(), now(), getServerUUID(parameters))));
placeholders.registerStatic("sessions_play_time_month",
parameters -> timeAmount.apply(database.query(SessionQueries.playtime(monthAgo(), now(), getServerUUID(parameters)))));
placeholders.registerStatic("sessions_play_time_month_raw",
parameters -> database.query(SessionQueries.playtime(monthAgo(), now(), getServerUUID(parameters))));
placeholders.registerStatic("sessions_active_time_total", registerServerPve(placeholders);
parameters -> timeAmount.apply(database.query(SessionQueries.activePlaytime(0L, now(), getServerUUID(parameters))))); registerSessionLength(placeholders);
placeholders.registerStatic("sessions_active_time_total_raw",
parameters -> database.query(SessionQueries.activePlaytime(0L, now(), getServerUUID(parameters))));
placeholders.registerStatic("sessions_active_time_day",
parameters -> timeAmount.apply(database.query(SessionQueries.activePlaytime(dayAgo(), now(), getServerUUID(parameters)))));
placeholders.registerStatic("sessions_active_time_day_raw",
parameters -> database.query(SessionQueries.activePlaytime(dayAgo(), now(), getServerUUID(parameters))));
placeholders.registerStatic("sessions_active_time_week",
parameters -> timeAmount.apply(database.query(SessionQueries.activePlaytime(weekAgo(), now(), getServerUUID(parameters)))));
placeholders.registerStatic("sessions_active_time_week_raw",
parameters -> database.query(SessionQueries.activePlaytime(weekAgo(), now(), getServerUUID(parameters))));
placeholders.registerStatic("sessions_active_time_month",
parameters -> timeAmount.apply(database.query(SessionQueries.activePlaytime(monthAgo(), now(), getServerUUID(parameters)))));
placeholders.registerStatic("sessions_active_time_month_raw",
parameters -> database.query(SessionQueries.activePlaytime(monthAgo(), now(), getServerUUID(parameters))));
placeholders.registerStatic("sessions_afk_time_total", registerServerUniquePlayers(placeholders);
parameters -> timeAmount.apply(database.query(SessionQueries.afkTime(0L, now(), getServerUUID(parameters))))); registerNetworkUniquePlayers(placeholders);
placeholders.registerStatic("sessions_afk_time_total_raw", registerAverageUniquePlayer(placeholders, tzOffsetMs);
parameters -> database.query(SessionQueries.afkTime(0L, now(), getServerUUID(parameters)))); registerNewPlayer(placeholders);
placeholders.registerStatic("sessions_afk_time_day",
parameters -> timeAmount.apply(database.query(SessionQueries.afkTime(dayAgo(), now(), getServerUUID(parameters)))));
placeholders.registerStatic("sessions_afk_time_day_raw",
parameters -> database.query(SessionQueries.afkTime(dayAgo(), now(), getServerUUID(parameters))));
placeholders.registerStatic("sessions_afk_time_week",
parameters -> timeAmount.apply(database.query(SessionQueries.afkTime(weekAgo(), now(), getServerUUID(parameters)))));
placeholders.registerStatic("sessions_afk_time_week_raw",
parameters -> database.query(SessionQueries.afkTime(weekAgo(), now(), getServerUUID(parameters))));
placeholders.registerStatic("sessions_afk_time_month",
parameters -> timeAmount.apply(database.query(SessionQueries.afkTime(monthAgo(), now(), getServerUUID(parameters)))));
placeholders.registerStatic("sessions_afk_time_month_raw",
parameters -> database.query(SessionQueries.afkTime(monthAgo(), now(), getServerUUID(parameters))));
registerPing(placeholders);
registerServerPeakCounts(placeholders);
}
private void registerServerPeakCounts(PlanPlaceholders placeholders) {
placeholders.registerStatic("sessions_peak_count",
parameters -> database.query(TPSQueries.fetchAllTimePeakPlayerCount(getServerUUID(parameters))).map(DateObj::getValue).orElse(0));
placeholders.registerStatic("sessions_peak_date",
parameters -> database.query(TPSQueries.fetchAllTimePeakPlayerCount(getServerUUID(parameters))).map(year).orElse("-"));
placeholders.registerStatic("sessions_recent_peak_count",
parameters -> database.query(TPSQueries.fetchPeakPlayerCount(getServerUUID(parameters), now() - TimeUnit.DAYS.toMillis(2L))).map(DateObj::getValue).orElse(0));
placeholders.registerStatic("sessions_recent_peak_date",
parameters -> database.query(TPSQueries.fetchPeakPlayerCount(getServerUUID(parameters), now() - TimeUnit.DAYS.toMillis(2L))).map(year).orElse("-"));
}
private void registerPing(PlanPlaceholders placeholders) {
placeholders.registerStatic("ping_total",
parameters -> decimals.apply(database.query(PingQueries.averagePing(0L, now(), getServerUUID(parameters)))) + " ms");
placeholders.registerStatic("ping_day",
parameters -> decimals.apply(database.query(PingQueries.averagePing(dayAgo(), now(), getServerUUID(parameters)))) + " ms");
placeholders.registerStatic("ping_week",
parameters -> decimals.apply(database.query(PingQueries.averagePing(weekAgo(), now(), getServerUUID(parameters)))) + " ms");
placeholders.registerStatic("ping_month",
parameters -> decimals.apply(database.query(PingQueries.averagePing(monthAgo(), now(), getServerUUID(parameters)))) + " ms");
placeholders.registerStatic("network_ping_total",
parameters -> decimals.apply(database.query(PingQueries.averagePing(0L, now()))) + " ms");
placeholders.registerStatic("network_ping_day",
parameters -> decimals.apply(database.query(PingQueries.averagePing(dayAgo(), now()))) + " ms");
placeholders.registerStatic("network_ping_week",
parameters -> decimals.apply(database.query(PingQueries.averagePing(weekAgo(), now()))) + " ms");
placeholders.registerStatic("network_ping_month",
parameters -> decimals.apply(database.query(PingQueries.averagePing(monthAgo(), now()))) + " ms");
}
private void registerNewPlayer(PlanPlaceholders placeholders) {
placeholders.registerStatic("sessions_new_players_day",
parameters -> database.query(PlayerCountQueries.newPlayerCount(dayAgo(), now(), getServerUUID(parameters))));
placeholders.registerStatic("sessions_new_players_week",
parameters -> database.query(PlayerCountQueries.newPlayerCount(weekAgo(), now(), getServerUUID(parameters))));
placeholders.registerStatic("sessions_new_players_month",
parameters -> database.query(PlayerCountQueries.newPlayerCount(monthAgo(), now(), getServerUUID(parameters))));
placeholders.registerStatic("network_sessions_new_players_day",
parameters -> database.query(PlayerCountQueries.newPlayerCount(dayAgo(), now())));
placeholders.registerStatic("network_sessions_new_players_week",
parameters -> database.query(PlayerCountQueries.newPlayerCount(weekAgo(), now())));
placeholders.registerStatic("network_sessions_new_players_month",
parameters -> database.query(PlayerCountQueries.newPlayerCount(monthAgo(), now())));
}
private void registerAverageUniquePlayer(PlanPlaceholders placeholders, int tzOffsetMs) {
placeholders.registerStatic("sessions_average_unique_players_total",
parameters -> database.query(PlayerCountQueries.averageUniquePlayerCount(0L, now(), tzOffsetMs, getServerUUID(parameters))));
placeholders.registerStatic("sessions_average_unique_players_day",
parameters -> database.query(PlayerCountQueries.averageUniquePlayerCount(dayAgo(), now(), tzOffsetMs, getServerUUID(parameters))));
placeholders.registerStatic("sessions_average_unique_players_week",
parameters -> database.query(PlayerCountQueries.averageUniquePlayerCount(weekAgo(), now(), tzOffsetMs, getServerUUID(parameters))));
placeholders.registerStatic("sessions_average_unique_players_month",
parameters -> database.query(PlayerCountQueries.averageUniquePlayerCount(monthAgo(), now(), tzOffsetMs, getServerUUID(parameters))));
placeholders.registerStatic("network_sessions_average_unique_players_total",
parameters -> database.query(PlayerCountQueries.averageUniquePlayerCount(0L, now(), tzOffsetMs)));
placeholders.registerStatic("network_sessions_average_unique_players_day",
parameters -> database.query(PlayerCountQueries.averageUniquePlayerCount(dayAgo(), now(), tzOffsetMs)));
placeholders.registerStatic("network_sessions_average_unique_players_week",
parameters -> database.query(PlayerCountQueries.averageUniquePlayerCount(weekAgo(), now(), tzOffsetMs)));
placeholders.registerStatic("network_sessions_average_unique_players_month",
parameters -> database.query(PlayerCountQueries.averageUniquePlayerCount(monthAgo(), now(), tzOffsetMs)));
}
private void registerSessionLength(PlanPlaceholders placeholders) {
placeholders.registerStatic("sessions_average_session_length_total",
parameters -> getPlaytime(database, 0L, now(), getServerUUID(parameters), timeAmount));
placeholders.registerStatic("sessions_average_session_length_day",
parameters -> getPlaytime(database, dayAgo(), now(), getServerUUID(parameters), timeAmount));
placeholders.registerStatic("sessions_average_session_length_week",
parameters -> getPlaytime(database, weekAgo(), now(), getServerUUID(parameters), timeAmount));
placeholders.registerStatic("sessions_average_session_length_month",
parameters -> getPlaytime(database, monthAgo(), now(), getServerUUID(parameters), timeAmount));
placeholders.registerStatic("network_sessions_average_session_length_total",
parameters -> getPlaytime(database, 0L, now(), timeAmount));
placeholders.registerStatic("network_sessions_average_session_length_day",
parameters -> getPlaytime(database, dayAgo(), now(), timeAmount));
placeholders.registerStatic("network_sessions_average_session_length_week",
parameters -> getPlaytime(database, weekAgo(), now(), timeAmount));
placeholders.registerStatic("network_sessions_average_session_length_month",
parameters -> getPlaytime(database, monthAgo(), now(), timeAmount));
}
private void registerNetworkUniquePlayers(PlanPlaceholders placeholders) {
PlanPlaceholders.StaticPlaceholderLoader networkUniquePlayers = parameters -> database.query(PlayerCountQueries.newPlayerCount(0L, now()));
placeholders.registerStatic("network_sessions_unique_players_total", networkUniquePlayers);
placeholders.registerStatic("network_sessions_new_players_total", networkUniquePlayers);
placeholders.registerStatic("network_sessions_unique_players_day",
parameters -> database.query(PlayerCountQueries.uniquePlayerCount(dayAgo(), now())));
placeholders.registerStatic("network_sessions_unique_players_today",
parameters -> {
NavigableMap<Long, Integer> playerCounts = database.query(PlayerCountQueries.uniquePlayerCounts(dayAgo(), now(), config.getTimeZone().getOffset(now())));
return playerCounts.isEmpty() ? 0 : playerCounts.lastEntry().getValue();
});
placeholders.registerStatic("network_sessions_unique_players_week",
parameters -> database.query(PlayerCountQueries.uniquePlayerCount(weekAgo(), now())));
placeholders.registerStatic("network_sessions_unique_players_month",
parameters -> database.query(PlayerCountQueries.uniquePlayerCount(monthAgo(), now())));
}
private void registerServerUniquePlayers(PlanPlaceholders placeholders) {
PlanPlaceholders.StaticPlaceholderLoader uniquePlayers = parameters -> database.query(PlayerCountQueries.newPlayerCount(0L, now(), getServerUUID(parameters))); PlanPlaceholders.StaticPlaceholderLoader uniquePlayers = parameters -> database.query(PlayerCountQueries.newPlayerCount(0L, now(), getServerUUID(parameters)));
placeholders.registerStatic("sessions_unique_players_total", uniquePlayers); placeholders.registerStatic("sessions_unique_players_total", uniquePlayers);
placeholders.registerStatic("sessions_new_players_total", uniquePlayers); placeholders.registerStatic("sessions_new_players_total", uniquePlayers);
@ -147,7 +234,9 @@ public class SessionPlaceHolders implements Placeholders {
parameters -> database.query(PlayerCountQueries.uniquePlayerCount(weekAgo(), now(), getServerUUID(parameters)))); parameters -> database.query(PlayerCountQueries.uniquePlayerCount(weekAgo(), now(), getServerUUID(parameters))));
placeholders.registerStatic("sessions_unique_players_month", placeholders.registerStatic("sessions_unique_players_month",
parameters -> database.query(PlayerCountQueries.uniquePlayerCount(monthAgo(), now(), getServerUUID(parameters)))); parameters -> database.query(PlayerCountQueries.uniquePlayerCount(monthAgo(), now(), getServerUUID(parameters))));
}
private void registerServerPve(PlanPlaceholders placeholders) {
placeholders.registerStatic("sessions_players_death_total", placeholders.registerStatic("sessions_players_death_total",
parameters -> database.query(KillQueries.deathCount(0L, now(), getServerUUID(parameters)))); parameters -> database.query(KillQueries.deathCount(0L, now(), getServerUUID(parameters))));
placeholders.registerStatic("sessions_players_death_day", placeholders.registerStatic("sessions_players_death_day",
@ -174,50 +263,120 @@ public class SessionPlaceHolders implements Placeholders {
parameters -> database.query(KillQueries.mobKillCount(weekAgo(), now(), getServerUUID(parameters)))); parameters -> database.query(KillQueries.mobKillCount(weekAgo(), now(), getServerUUID(parameters))));
placeholders.registerStatic("sessions_mob_kill_month", placeholders.registerStatic("sessions_mob_kill_month",
parameters -> database.query(KillQueries.mobKillCount(monthAgo(), now(), getServerUUID(parameters)))); parameters -> database.query(KillQueries.mobKillCount(monthAgo(), now(), getServerUUID(parameters))));
}
placeholders.registerStatic("sessions_average_session_length_total", private void registerNetworkAfkTime(PlanPlaceholders placeholders) {
parameters -> getPlaytime(database, 0L, now(), getServerUUID(parameters), timeAmount)); placeholders.registerStatic("network_sessions_afk_time_total",
placeholders.registerStatic("sessions_average_session_length_day", parameters -> timeAmount.apply(database.query(SessionQueries.afkTime(0L, now()))));
parameters -> getPlaytime(database, dayAgo(), now(), getServerUUID(parameters), timeAmount)); placeholders.registerStatic("network_sessions_afk_time_total_raw",
placeholders.registerStatic("sessions_average_session_length_week", parameters -> database.query(SessionQueries.afkTime(0L, now())));
parameters -> getPlaytime(database, weekAgo(), now(), getServerUUID(parameters), timeAmount)); placeholders.registerStatic("network_sessions_afk_time_day",
placeholders.registerStatic("sessions_average_session_length_month", parameters -> timeAmount.apply(database.query(SessionQueries.afkTime(dayAgo(), now()))));
parameters -> getPlaytime(database, monthAgo(), now(), getServerUUID(parameters), timeAmount)); placeholders.registerStatic("network_sessions_afk_time_day_raw",
parameters -> database.query(SessionQueries.afkTime(dayAgo(), now())));
placeholders.registerStatic("network_sessions_afk_time_week",
parameters -> timeAmount.apply(database.query(SessionQueries.afkTime(weekAgo(), now()))));
placeholders.registerStatic("network_sessions_afk_time_week_raw",
parameters -> database.query(SessionQueries.afkTime(weekAgo(), now())));
placeholders.registerStatic("network_sessions_afk_time_month",
parameters -> timeAmount.apply(database.query(SessionQueries.afkTime(monthAgo(), now()))));
placeholders.registerStatic("network_sessions_afk_time_month_raw",
parameters -> database.query(SessionQueries.afkTime(monthAgo(), now())));
}
placeholders.registerStatic("sessions_average_unique_players_total", private void registerServerAfkTime(PlanPlaceholders placeholders) {
parameters -> database.query(PlayerCountQueries.averageUniquePlayerCount(0L, now(), tzOffsetMs, getServerUUID(parameters)))); placeholders.registerStatic("sessions_afk_time_total",
placeholders.registerStatic("sessions_average_unique_players_day", parameters -> timeAmount.apply(database.query(SessionQueries.afkTime(0L, now(), getServerUUID(parameters)))));
parameters -> database.query(PlayerCountQueries.averageUniquePlayerCount(dayAgo(), now(), tzOffsetMs, getServerUUID(parameters)))); placeholders.registerStatic("sessions_afk_time_total_raw",
placeholders.registerStatic("sessions_average_unique_players_week", parameters -> database.query(SessionQueries.afkTime(0L, now(), getServerUUID(parameters))));
parameters -> database.query(PlayerCountQueries.averageUniquePlayerCount(weekAgo(), now(), tzOffsetMs, getServerUUID(parameters)))); placeholders.registerStatic("sessions_afk_time_day",
placeholders.registerStatic("sessions_average_unique_players_month", parameters -> timeAmount.apply(database.query(SessionQueries.afkTime(dayAgo(), now(), getServerUUID(parameters)))));
parameters -> database.query(PlayerCountQueries.averageUniquePlayerCount(monthAgo(), now(), tzOffsetMs, getServerUUID(parameters)))); placeholders.registerStatic("sessions_afk_time_day_raw",
parameters -> database.query(SessionQueries.afkTime(dayAgo(), now(), getServerUUID(parameters))));
placeholders.registerStatic("sessions_afk_time_week",
parameters -> timeAmount.apply(database.query(SessionQueries.afkTime(weekAgo(), now(), getServerUUID(parameters)))));
placeholders.registerStatic("sessions_afk_time_week_raw",
parameters -> database.query(SessionQueries.afkTime(weekAgo(), now(), getServerUUID(parameters))));
placeholders.registerStatic("sessions_afk_time_month",
parameters -> timeAmount.apply(database.query(SessionQueries.afkTime(monthAgo(), now(), getServerUUID(parameters)))));
placeholders.registerStatic("sessions_afk_time_month_raw",
parameters -> database.query(SessionQueries.afkTime(monthAgo(), now(), getServerUUID(parameters))));
}
placeholders.registerStatic("sessions_new_players_day", private void registerNetworkActivePlaytime(PlanPlaceholders placeholders) {
parameters -> database.query(PlayerCountQueries.newPlayerCount(dayAgo(), now(), getServerUUID(parameters)))); placeholders.registerStatic("network_sessions_active_time_total",
placeholders.registerStatic("sessions_new_players_week", parameters -> timeAmount.apply(database.query(SessionQueries.activePlaytime(0L, now()))));
parameters -> database.query(PlayerCountQueries.newPlayerCount(weekAgo(), now(), getServerUUID(parameters)))); placeholders.registerStatic("network_sessions_active_time_total_raw",
placeholders.registerStatic("sessions_new_players_month", parameters -> database.query(SessionQueries.activePlaytime(0L, now())));
parameters -> database.query(PlayerCountQueries.newPlayerCount(monthAgo(), now(), getServerUUID(parameters)))); placeholders.registerStatic("network_sessions_active_time_day",
parameters -> timeAmount.apply(database.query(SessionQueries.activePlaytime(dayAgo(), now()))));
placeholders.registerStatic("network_sessions_active_time_day_raw",
parameters -> database.query(SessionQueries.activePlaytime(dayAgo(), now())));
placeholders.registerStatic("network_sessions_active_time_week",
parameters -> timeAmount.apply(database.query(SessionQueries.activePlaytime(weekAgo(), now()))));
placeholders.registerStatic("network_sessions_active_time_week_raw",
parameters -> database.query(SessionQueries.activePlaytime(weekAgo(), now())));
placeholders.registerStatic("network_sessions_active_time_month",
parameters -> timeAmount.apply(database.query(SessionQueries.activePlaytime(monthAgo(), now()))));
placeholders.registerStatic("network_sessions_active_time_month_raw",
parameters -> database.query(SessionQueries.activePlaytime(monthAgo(), now())));
}
placeholders.registerStatic("ping_total", private void registerServerActivePlaytime(PlanPlaceholders placeholders) {
parameters -> decimals.apply(database.query(PingQueries.averagePing(0L, now(), getServerUUID(parameters)))) + " ms"); placeholders.registerStatic("sessions_active_time_total",
placeholders.registerStatic("ping_day", parameters -> timeAmount.apply(database.query(SessionQueries.activePlaytime(0L, now(), getServerUUID(parameters)))));
parameters -> decimals.apply(database.query(PingQueries.averagePing(dayAgo(), now(), getServerUUID(parameters)))) + " ms"); placeholders.registerStatic("sessions_active_time_total_raw",
placeholders.registerStatic("ping_week", parameters -> database.query(SessionQueries.activePlaytime(0L, now(), getServerUUID(parameters))));
parameters -> decimals.apply(database.query(PingQueries.averagePing(weekAgo(), now(), getServerUUID(parameters)))) + " ms"); placeholders.registerStatic("sessions_active_time_day",
placeholders.registerStatic("ping_month", parameters -> timeAmount.apply(database.query(SessionQueries.activePlaytime(dayAgo(), now(), getServerUUID(parameters)))));
parameters -> decimals.apply(database.query(PingQueries.averagePing(monthAgo(), now(), getServerUUID(parameters)))) + " ms"); placeholders.registerStatic("sessions_active_time_day_raw",
parameters -> database.query(SessionQueries.activePlaytime(dayAgo(), now(), getServerUUID(parameters))));
placeholders.registerStatic("sessions_active_time_week",
parameters -> timeAmount.apply(database.query(SessionQueries.activePlaytime(weekAgo(), now(), getServerUUID(parameters)))));
placeholders.registerStatic("sessions_active_time_week_raw",
parameters -> database.query(SessionQueries.activePlaytime(weekAgo(), now(), getServerUUID(parameters))));
placeholders.registerStatic("sessions_active_time_month",
parameters -> timeAmount.apply(database.query(SessionQueries.activePlaytime(monthAgo(), now(), getServerUUID(parameters)))));
placeholders.registerStatic("sessions_active_time_month_raw",
parameters -> database.query(SessionQueries.activePlaytime(monthAgo(), now(), getServerUUID(parameters))));
}
placeholders.registerStatic("sessions_peak_count", private void registerNetworkPlaytime(PlanPlaceholders placeholders) {
parameters -> database.query(TPSQueries.fetchAllTimePeakPlayerCount(getServerUUID(parameters))).map(DateObj::getValue).orElse(0)); placeholders.registerStatic("network_sessions_play_time_total",
placeholders.registerStatic("sessions_peak_date", parameters -> timeAmount.apply(database.query(SessionQueries.playtime(0L, now()))));
parameters -> database.query(TPSQueries.fetchAllTimePeakPlayerCount(getServerUUID(parameters))).map(year).orElse("-")); placeholders.registerStatic("network_sessions_play_time_total_raw",
parameters -> database.query(SessionQueries.playtime(0L, now())));
placeholders.registerStatic("network_sessions_play_time_day",
parameters -> timeAmount.apply(database.query(SessionQueries.playtime(dayAgo(), now()))));
placeholders.registerStatic("network_sessions_play_time_day_raw",
parameters -> database.query(SessionQueries.playtime(dayAgo(), now())));
placeholders.registerStatic("network_sessions_play_time_week",
parameters -> timeAmount.apply(database.query(SessionQueries.playtime(weekAgo(), now()))));
placeholders.registerStatic("network_sessions_play_time_week_raw",
parameters -> database.query(SessionQueries.playtime(weekAgo(), now())));
placeholders.registerStatic("network_sessions_play_time_month",
parameters -> timeAmount.apply(database.query(SessionQueries.playtime(monthAgo(), now()))));
placeholders.registerStatic("network_sessions_play_time_month_raw",
parameters -> database.query(SessionQueries.playtime(monthAgo(), now())));
}
placeholders.registerStatic("sessions_recent_peak_count", private void registerServerPlaytime(PlanPlaceholders placeholders) {
parameters -> database.query(TPSQueries.fetchPeakPlayerCount(getServerUUID(parameters), now() - TimeUnit.DAYS.toMillis(2L))).map(DateObj::getValue).orElse(0)); placeholders.registerStatic("sessions_play_time_total",
placeholders.registerStatic("sessions_recent_peak_date", parameters -> timeAmount.apply(database.query(SessionQueries.playtime(0L, now(), getServerUUID(parameters)))));
parameters -> database.query(TPSQueries.fetchPeakPlayerCount(getServerUUID(parameters), now() - TimeUnit.DAYS.toMillis(2L))).map(year).orElse("-")); placeholders.registerStatic("sessions_play_time_total_raw",
parameters -> database.query(SessionQueries.playtime(0L, now(), getServerUUID(parameters))));
placeholders.registerStatic("sessions_play_time_day",
parameters -> timeAmount.apply(database.query(SessionQueries.playtime(dayAgo(), now(), getServerUUID(parameters)))));
placeholders.registerStatic("sessions_play_time_day_raw",
parameters -> database.query(SessionQueries.playtime(dayAgo(), now(), getServerUUID(parameters))));
placeholders.registerStatic("sessions_play_time_week",
parameters -> timeAmount.apply(database.query(SessionQueries.playtime(weekAgo(), now(), getServerUUID(parameters)))));
placeholders.registerStatic("sessions_play_time_week_raw",
parameters -> database.query(SessionQueries.playtime(weekAgo(), now(), getServerUUID(parameters))));
placeholders.registerStatic("sessions_play_time_month",
parameters -> timeAmount.apply(database.query(SessionQueries.playtime(monthAgo(), now(), getServerUUID(parameters)))));
placeholders.registerStatic("sessions_play_time_month_raw",
parameters -> database.query(SessionQueries.playtime(monthAgo(), now(), getServerUUID(parameters))));
} }
private ServerUUID getServerUUID(@Untrusted Arguments parameters) { private ServerUUID getServerUUID(@Untrusted Arguments parameters) {

View File

@ -200,6 +200,14 @@ public class ConfigNode {
return key; return key;
} }
private String getEnvironmentVariableKey() {
String deepKey = parent != null ? parent.getKey(true) + "." + key : "";
if (deepKey.startsWith(".")) {
deepKey = deepKey.substring(1);
}
return "PLAN_" + StringUtils.replaceChars(StringUtils.upperCase(deepKey), '.', '_');
}
public void sort() { public void sort() {
Collections.sort(nodeOrder); Collections.sort(nodeOrder);
} }
@ -255,32 +263,51 @@ public class ConfigNode {
this.comment = comment; this.comment = comment;
} }
private String getEnvironmentVariable() {
String key = getEnvironmentVariableKey();
String variable = System.getenv(key);
Map<String, String> env = System.getenv();
return variable;
}
public List<String> getStringList() { public List<String> getStringList() {
String environmentVariable = getEnvironmentVariable();
if (environmentVariable != null) return new ConfigValueParser.StringListParser().compose(environmentVariable);
return value == null ? Collections.emptyList() return value == null ? Collections.emptyList()
: new ConfigValueParser.StringListParser().compose(value); : new ConfigValueParser.StringListParser().compose(value);
} }
public Integer getInteger() { public Integer getInteger() {
String environmentVariable = getEnvironmentVariable();
if (environmentVariable != null) return new ConfigValueParser.IntegerParser().compose(environmentVariable);
return value == null ? null return value == null ? null
: new ConfigValueParser.IntegerParser().compose(value); : new ConfigValueParser.IntegerParser().compose(value);
} }
public Long getLong() { public Long getLong() {
String environmentVariable = getEnvironmentVariable();
if (environmentVariable != null) return new ConfigValueParser.LongParser().compose(environmentVariable);
return value == null ? null return value == null ? null
: new ConfigValueParser.LongParser().compose(value); : new ConfigValueParser.LongParser().compose(value);
} }
public String getString() { public String getString() {
String environmentVariable = getEnvironmentVariable();
if (environmentVariable != null) return new ConfigValueParser.StringParser().compose(environmentVariable);
return value == null ? null return value == null ? null
: new ConfigValueParser.StringParser().compose(value); : new ConfigValueParser.StringParser().compose(value);
} }
public Double getDouble() { public Double getDouble() {
String environmentVariable = getEnvironmentVariable();
if (environmentVariable != null) return new ConfigValueParser.DoubleParser().compose(environmentVariable);
return value == null ? null return value == null ? null
: new ConfigValueParser.DoubleParser().compose(value); : new ConfigValueParser.DoubleParser().compose(value);
} }
public boolean getBoolean() { public boolean getBoolean() {
String environmentVariable = getEnvironmentVariable();
if (environmentVariable != null) return new ConfigValueParser.BooleanParser().compose(environmentVariable);
return new ConfigValueParser.BooleanParser().compose(value); return new ConfigValueParser.BooleanParser().compose(value);
} }

View File

@ -39,6 +39,8 @@ public interface ConfigValueParser<T> {
return new BooleanParser(); return new BooleanParser();
} else if (Long.class.isAssignableFrom(type)) { } else if (Long.class.isAssignableFrom(type)) {
return new LongParser(); return new LongParser();
} else if (Double.class.isAssignableFrom(type)) {
return new DoubleParser();
} else if (Integer.class.isAssignableFrom(type)) { } else if (Integer.class.isAssignableFrom(type)) {
return new IntegerParser(); return new IntegerParser();
} }

View File

@ -177,6 +177,9 @@ public class ConfigUpdater {
new ConfigChange.Removed("Plugin.Use_Legacy_Frontend"), new ConfigChange.Removed("Plugin.Use_Legacy_Frontend"),
new ConfigChange.Removed("Customized_files.Enable_web_dev_mode"), new ConfigChange.Removed("Customized_files.Enable_web_dev_mode"),
new ConfigChange.Removed("Customized_files.Plan"), new ConfigChange.Removed("Customized_files.Plan"),
new ConfigChange.Moved("Data_gathering.Preserve_join_address_case", "Data_gathering.Join_addresses.Preserve_case"),
new ConfigChange.Moved("Data_gathering.Preserve_invalid_join_addresses", "Data_gathering.Join_addresses.Preserve_invalid"),
}; };
} }

View File

@ -18,6 +18,10 @@ package com.djrapitops.plan.settings.config.paths;
import com.djrapitops.plan.settings.config.paths.key.BooleanSetting; import com.djrapitops.plan.settings.config.paths.key.BooleanSetting;
import com.djrapitops.plan.settings.config.paths.key.Setting; import com.djrapitops.plan.settings.config.paths.key.Setting;
import com.djrapitops.plan.settings.config.paths.key.StringListSetting;
import com.djrapitops.plan.settings.config.paths.key.StringSetting;
import java.util.List;
/** /**
* {@link Setting} values that are in "Data_gathering" section. * {@link Setting} values that are in "Data_gathering" section.
@ -28,11 +32,15 @@ public class DataGatheringSettings {
public static final Setting<Boolean> GEOLOCATIONS = new BooleanSetting("Data_gathering.Geolocations"); public static final Setting<Boolean> GEOLOCATIONS = new BooleanSetting("Data_gathering.Geolocations");
public static final Setting<Boolean> ACCEPT_GEOLITE2_EULA = new BooleanSetting("Data_gathering.Accept_GeoLite2_EULA"); public static final Setting<Boolean> ACCEPT_GEOLITE2_EULA = new BooleanSetting("Data_gathering.Accept_GeoLite2_EULA");
public static final Setting<String> GEOLOCATION_DOWNLOAD_URL = new StringSetting("Data_gathering.Geolocation_Download_URL");
public static final Setting<Boolean> PING = new BooleanSetting("Data_gathering.Ping"); public static final Setting<Boolean> PING = new BooleanSetting("Data_gathering.Ping");
public static final Setting<Boolean> DISK_SPACE = new BooleanSetting("Data_gathering.Disk_space"); public static final Setting<Boolean> DISK_SPACE = new BooleanSetting("Data_gathering.Disk_space");
public static final Setting<Boolean> LOG_UNKNOWN_COMMANDS = new BooleanSetting("Data_gathering.Commands.Log_unknown"); public static final Setting<Boolean> LOG_UNKNOWN_COMMANDS = new BooleanSetting("Data_gathering.Commands.Log_unknown");
public static final Setting<Boolean> COMBINE_COMMAND_ALIASES = new BooleanSetting("Data_gathering.Commands.Log_aliases_as_main_command"); public static final Setting<Boolean> COMBINE_COMMAND_ALIASES = new BooleanSetting("Data_gathering.Commands.Log_aliases_as_main_command");
public static final Setting<Boolean> PRESERVE_JOIN_ADDRESS_CASE = new BooleanSetting("Data_gathering.Preserve_join_address_case"); public static final Setting<Boolean> JOIN_ADDRESSES = new BooleanSetting("Data_gathering.Join_addresses.Enabled");
public static final Setting<Boolean> PRESERVE_JOIN_ADDRESS_CASE = new BooleanSetting("Data_gathering.Join_addresses.Preserve_case");
public static final Setting<Boolean> PRESERVE_INVALID_JOIN_ADDRESS = new BooleanSetting("Data_gathering.Join_addresses.Preserve_invalid");
public static final Setting<List<String>> FILTER_JOIN_ADDRESSES = new StringListSetting("Data_gathering.Join_addresses.Filter_out_from_data");
private DataGatheringSettings() { private DataGatheringSettings() {
/* static variable class */ /* static variable class */

View File

@ -26,11 +26,11 @@ public enum LangCode {
CUSTOM("Custom", ""), CUSTOM("Custom", ""),
EN("English", "AuroraLS3"), EN("English", "AuroraLS3"),
ES("Español", "Catalina, itaquito, Elguerrero & 4drian3d"), ES("Español", "Catalina, itaquito, Elguerrero & 4drian3d"),
CN("\u6C49\u8BED", "f0rb1d (\u4f5b\u58c1\u706f), qsefthuopq, shaokeyibb, Fur_xia, 10935336, SkipM4, TheLittle_Yang & jhqwqmc"), // Simplified Chinese CN("\u6C49\u8BED", "f0rb1d (\u4f5b\u58c1\u706f), qsefthuopq, shaokeyibb, Fur_xia, 10935336, SkipM4, TheLittle_Yang, jhqwqmc & liuzhen932"), // Simplified Chinese
CS("\u010de\u0161tina", "Shadowhackercz, QuakyCZ, MrFriggo & WolverStones"), CS("\u010de\u0161tina", "Shadowhackercz, QuakyCZ, MrFriggo & WolverStones"),
DE("Deutsch", "Eyremba, fuzzlemann, Morsmorse, hallo1142 & DubHacker"), DE("Deutsch", "Eyremba, fuzzlemann, Morsmorse, hallo1142 & DubHacker"),
FI("suomi", "AuroraLS3, KasperiP"), FI("suomi", "AuroraLS3, KasperiP"),
FR("français", "CyanTech, Aurelien & Nogapra"), FR("français", "CyanTech, Aurelien, Nogapra & Sniper_TVmc"),
IT("Italiano", "Malachiel & Mastory_Md5"), IT("Italiano", "Malachiel & Mastory_Md5"),
JA("\u65E5\u672C\u8A9E", "yukieji, inductor, lis2a, yu_solt , Jumala9163 & ringoXD"), JA("\u65E5\u672C\u8A9E", "yukieji, inductor, lis2a, yu_solt , Jumala9163 & ringoXD"),
KO("\uD55C\uAD6D\uC5B4", "Guinness_Akihiko"), KO("\uD55C\uAD6D\uC5B4", "Guinness_Akihiko"),

View File

@ -108,7 +108,7 @@ public class LocaleSystem implements SubSystem {
HtmlLang.values(), HtmlLang.values(),
JSLang.values(), JSLang.values(),
PluginLang.values(), PluginLang.values(),
WebPermission.values(), WebPermission.nonDeprecatedValues(),
}; };
} }

View File

@ -219,6 +219,8 @@ public enum HtmlLang implements Lang {
LABEL_TITLE_SERVER_CALENDAR("html.label.serverCalendar", "Server Calendar"), LABEL_TITLE_SERVER_CALENDAR("html.label.serverCalendar", "Server Calendar"),
LABEL_TITLE_NETWORK_CALENDAR("html.label.networkCalendar", "Network Calendar"), LABEL_TITLE_NETWORK_CALENDAR("html.label.networkCalendar", "Network Calendar"),
LABEL_LABEL_JOIN_ADDRESS("html.label.joinAddress", "Join Address"), LABEL_LABEL_JOIN_ADDRESS("html.label.joinAddress", "Join Address"),
LABEL_ADD_JOIN_ADDRESS_GROUP("html.label.addJoinAddressGroup", "Add address group"),
LABEL_ADDRESS_GROUP("html.label.addressGroup", "Address group {{n}}"),
LABEL_LABEL_SESSION_MEDIAN("html.label.medianSessionLength", "Median Session Length"), LABEL_LABEL_SESSION_MEDIAN("html.label.medianSessionLength", "Median Session Length"),
LABEL_LABEL_KDR("html.label.kdr", "KDR"), LABEL_LABEL_KDR("html.label.kdr", "KDR"),
LABEL_TITLE_INSIGHTS("html.label.insights", "Insights"), LABEL_TITLE_INSIGHTS("html.label.insights", "Insights"),
@ -283,6 +285,16 @@ public enum HtmlLang implements Lang {
LABEL_TABLE_VISIBLE_COLUMNS("html.label.table.visibleColumns", "Visible columns"), LABEL_TABLE_VISIBLE_COLUMNS("html.label.table.visibleColumns", "Visible columns"),
LABEL_TABLE_SHOW_N_OF_M("html.label.table.showNofM", "Showing {{n}} of {{m}} entries"), LABEL_TABLE_SHOW_N_OF_M("html.label.table.showNofM", "Showing {{n}} of {{m}} entries"),
LABEL_TABLE_SHOW_PER_PAGE("html.label.table.showPerPage", "Show per page"), LABEL_TABLE_SHOW_PER_PAGE("html.label.table.showPerPage", "Show per page"),
LABEL_EXPORT("html.label.export", "Export"),
LABEL_ALLOWLIST("html.label.allowlist", "Allowlist"),
LABEL_ALLOWLIST_BOUNCES("html.label.allowlistBounces", "Allowlist Bounces"),
LABEL_ATTEMPTS("html.label.attempts", "Attempts"),
LABEL_LAST_KNOWN_ATTEMPT("html.label.lastKnownAttempt", "Last Known Attempt"),
LABEL_PREVIOUS_ATTEMPT("html.label.lastBlocked", "Last Blocked"),
LABEL_LAST_ALLOWED_LOGIN("html.label.lastAllowed", "Last Allowed"),
LABEL_BLOCKED("html.label.blocked", "Blocked"),
LABEL_ALLOWED("html.label.allowed", "Allowed"),
LOGIN_LOGIN("html.login.login", "Login"), LOGIN_LOGIN("html.login.login", "Login"),
LOGIN_LOGOUT("html.login.logout", "Logout"), LOGIN_LOGOUT("html.login.logout", "Logout"),
@ -326,6 +338,7 @@ public enum HtmlLang implements Lang {
QUERY_SERVERS_TWO("html.query.label.servers.two", "using data of 2 servers"), QUERY_SERVERS_TWO("html.query.label.servers.two", "using data of 2 servers"),
QUERY_SERVERS_MANY("html.query.label.servers.many", "using data of {number} servers"), QUERY_SERVERS_MANY("html.query.label.servers.many", "using data of {number} servers"),
QUERY_SHOW_FULL_QUERY("html.query.label.showFullQuery", "Show Full Query"), QUERY_SHOW_FULL_QUERY("html.query.label.showFullQuery", "Show Full Query"),
QUERY_EDIT_QUERY("html.query.label.editQuery", "Edit Query"),
HELP_TEST_RESULT("html.label.help.testResult", "Test result"), HELP_TEST_RESULT("html.label.help.testResult", "Test result"),
HELP_TEST_IT_OUT("html.label.help.testPrompt", "Test it out:"), HELP_TEST_IT_OUT("html.label.help.testPrompt", "Test it out:"),

View File

@ -84,6 +84,7 @@ public abstract class SQLDB extends AbstractDatabase {
private Supplier<ExecutorService> transactionExecutorServiceProvider; private Supplier<ExecutorService> transactionExecutorServiceProvider;
private ExecutorService transactionExecutor; private ExecutorService transactionExecutor;
private static final ThreadLocal<StackTraceElement[]> TRANSACTION_ORIGIN = new ThreadLocal<>();
private final AtomicInteger transactionQueueSize = new AtomicInteger(0); private final AtomicInteger transactionQueueSize = new AtomicInteger(0);
private final AtomicBoolean dropUnimportantTransactions = new AtomicBoolean(false); private final AtomicBoolean dropUnimportantTransactions = new AtomicBoolean(false);
@ -146,9 +147,13 @@ public abstract class SQLDB extends AbstractDatabase {
} }
} }
public static ThreadLocal<StackTraceElement[]> getTransactionOrigin() {
return TRANSACTION_ORIGIN;
}
@Override @Override
public void init() { public void init() {
List<Runnable> unfinishedTransactions = closeTransactionExecutor(transactionExecutor); List<Runnable> unfinishedTransactions = forceCloseTransactionExecutor();
this.transactionExecutor = transactionExecutorServiceProvider.get(); this.transactionExecutor = transactionExecutorServiceProvider.get();
setState(State.PATCHING); setState(State.PATCHING);
@ -167,9 +172,9 @@ public abstract class SQLDB extends AbstractDatabase {
} }
} }
private List<Runnable> closeTransactionExecutor(ExecutorService transactionExecutor) { protected boolean attemptToCloseTransactionExecutor() {
if (transactionExecutor == null || transactionExecutor.isShutdown() || transactionExecutor.isTerminated()) { if (transactionExecutor == null || transactionExecutor.isShutdown() || transactionExecutor.isTerminated()) {
return Collections.emptyList(); return true;
} }
transactionExecutor.shutdown(); transactionExecutor.shutdown();
try { try {
@ -179,20 +184,11 @@ public abstract class SQLDB extends AbstractDatabase {
logger.warn(TimeSettings.DB_TRANSACTION_FINISH_WAIT_DELAY.getPath() + " was set to over 5 minutes, using 5 min instead."); logger.warn(TimeSettings.DB_TRANSACTION_FINISH_WAIT_DELAY.getPath() + " was set to over 5 minutes, using 5 min instead.");
waitMs = TimeUnit.MINUTES.toMillis(5L); waitMs = TimeUnit.MINUTES.toMillis(5L);
} }
if (!transactionExecutor.awaitTermination(waitMs, TimeUnit.MILLISECONDS)) { return transactionExecutor.awaitTermination(waitMs, TimeUnit.MILLISECONDS);
List<Runnable> unfinished = transactionExecutor.shutdownNow();
int unfinishedCount = unfinished.size();
if (unfinishedCount > 0) {
logger.warn(unfinishedCount + " unfinished database transactions were not executed.");
}
return unfinished;
}
} catch (InterruptedException e) { } catch (InterruptedException e) {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
} finally {
logger.info(locale.getString(PluginLang.DISABLED_WAITING_TRANSACTIONS_COMPLETE));
} }
return Collections.emptyList(); return true;
} }
Patch[] patches() { Patch[] patches() {
@ -305,19 +301,33 @@ public abstract class SQLDB extends AbstractDatabase {
*/ */
public abstract void setupDataSource(); public abstract void setupDataSource();
@Override protected List<Runnable> forceCloseTransactionExecutor() {
public void close() { if (transactionExecutor == null || transactionExecutor.isShutdown() || transactionExecutor.isTerminated()) {
if (getState() == State.OPEN) setState(State.CLOSING); return Collections.emptyList();
closeTransactionExecutor(transactionExecutor); }
unloadDriverClassloader(); try {
setState(State.CLOSED); List<Runnable> unfinished = transactionExecutor.shutdownNow();
int unfinishedCount = unfinished.size();
if (unfinishedCount > 0) {
logger.warn(unfinishedCount + " unfinished database transactions were not executed.");
}
return unfinished;
} finally {
logger.info(locale.getString(PluginLang.DISABLED_WAITING_TRANSACTIONS_COMPLETE));
}
} }
private void unloadDriverClassloader() { @Override
// Unloading class loader using close() causes issues when reloading. public void close() {
// It is better to leak this memory than crash the plugin on reload. // SQLiteDB Overrides this, so any additions to this should also be reflected there.
if (getState() == State.OPEN) setState(State.CLOSING);
driverClassLoader = null; if (attemptToCloseTransactionExecutor()) {
logger.info(locale.getString(PluginLang.DISABLED_WAITING_TRANSACTIONS_COMPLETE));
} else {
forceCloseTransactionExecutor();
}
unloadDriverClassloader();
setState(State.CLOSED);
} }
public abstract Connection getConnection() throws SQLException; public abstract Connection getConnection() throws SQLException;
@ -333,13 +343,20 @@ public abstract class SQLDB extends AbstractDatabase {
return accessLock.performDatabaseOperation(() -> query.executeQuery(this), transaction); return accessLock.performDatabaseOperation(() -> query.executeQuery(this), transaction);
} }
protected void unloadDriverClassloader() {
// Unloading class loader using close() causes issues when reloading.
// It is better to leak this memory than crash the plugin on reload.
driverClassLoader = null;
}
@Override @Override
public CompletableFuture<?> executeTransaction(Transaction transaction) { public CompletableFuture<?> executeTransaction(Transaction transaction) {
if (getState() == State.CLOSED) { if (getState() == State.CLOSED) {
throw new DBClosedException("Transaction tried to execute although database is closed."); throw new DBClosedException("Transaction tried to execute although database is closed.");
} }
Exception origin = new Exception(); StackTraceElement[] origin = Thread.currentThread().getStackTrace();
if (determineIfShouldDropUnimportantTransactions(transactionQueueSize.incrementAndGet()) if (determineIfShouldDropUnimportantTransactions(transactionQueueSize.incrementAndGet())
&& transaction instanceof ThrowawayTransaction) { && transaction instanceof ThrowawayTransaction) {
@ -349,17 +366,24 @@ public abstract class SQLDB extends AbstractDatabase {
return CompletableFuture.supplyAsync(() -> { return CompletableFuture.supplyAsync(() -> {
try { try {
TRANSACTION_ORIGIN.set(origin);
if (getState() == State.CLOSED) return CompletableFuture.completedFuture(null);
accessLock.performDatabaseOperation(() -> { accessLock.performDatabaseOperation(() -> {
if (!ranIntoFatalError.get()) {transaction.executeTransaction(this);} if (!ranIntoFatalError.get()) {transaction.executeTransaction(this);}
}, transaction); }, transaction);
return CompletableFuture.completedFuture(null); return CompletableFuture.completedFuture(null);
} finally { } finally {
transactionQueueSize.decrementAndGet(); transactionQueueSize.decrementAndGet();
TRANSACTION_ORIGIN.remove();
} }
}, getTransactionExecutor()).exceptionally(errorHandler(transaction, origin)); }, getTransactionExecutor()).exceptionally(errorHandler(transaction, origin));
} }
private boolean determineIfShouldDropUnimportantTransactions(int queueSize) { private boolean determineIfShouldDropUnimportantTransactions(int queueSize) {
if (getState() == State.CLOSING) {
return true;
}
boolean dropTransactions = dropUnimportantTransactions.get(); boolean dropTransactions = dropUnimportantTransactions.get();
if (queueSize >= 500 && !dropTransactions) { if (queueSize >= 500 && !dropTransactions) {
logger.warn("Database queue size: " + queueSize + ", dropping some unimportant transactions. If this keeps happening disable some extensions or optimize MySQL."); logger.warn("Database queue size: " + queueSize + ", dropping some unimportant transactions. If this keeps happening disable some extensions or optimize MySQL.");
@ -372,7 +396,7 @@ public abstract class SQLDB extends AbstractDatabase {
return dropTransactions; return dropTransactions;
} }
private Function<Throwable, CompletableFuture<Object>> errorHandler(Transaction transaction, Exception origin) { private Function<Throwable, CompletableFuture<Object>> errorHandler(Transaction transaction, StackTraceElement[] origin) {
return throwable -> { return throwable -> {
if (throwable == null) { if (throwable == null) {
return CompletableFuture.completedFuture(null); return CompletableFuture.completedFuture(null);

View File

@ -59,7 +59,7 @@ public class SQLiteDB extends SQLDB {
* one thread closing the connection while another is executing a statement as * one thread closing the connection while another is executing a statement as
* that might lead to a SIGSEGV signal JVM crash. * that might lead to a SIGSEGV signal JVM crash.
*/ */
private final SemaphoreAccessCounter connectionLock = new SemaphoreAccessCounter(); private final SemaphoreAccessCounter connectionLock;
private Constructor<?> connectionConstructor; private Constructor<?> connectionConstructor;
@ -76,6 +76,7 @@ public class SQLiteDB extends SQLDB {
super(() -> serverInfo.get().getServerUUID(), locale, config, files, runnableFactory, logger, errorLogger); super(() -> serverInfo.get().getServerUUID(), locale, config, files, runnableFactory, logger, errorLogger);
dbName = databaseFile.getName(); dbName = databaseFile.getName();
this.databaseFile = databaseFile; this.databaseFile = databaseFile;
connectionLock = new SemaphoreAccessCounter();
} }
@Override @Override
@ -200,11 +201,21 @@ public class SQLiteDB extends SQLDB {
@Override @Override
public void close() { public void close() {
super.close(); if (getState() == State.OPEN) setState(State.CLOSING);
boolean transactionQueueClosed = attemptToCloseTransactionExecutor();
if (transactionQueueClosed) logger.info(locale.getString(PluginLang.DISABLED_WAITING_TRANSACTIONS_COMPLETE));
unloadDriverClassloader();
setState(State.CLOSED);
stopConnectionPingTask(); stopConnectionPingTask();
logger.info(locale.getString(PluginLang.DISABLED_WAITING_SQLITE)); logger.info(locale.getString(PluginLang.DISABLED_WAITING_SQLITE));
connectionLock.waitUntilNothingAccessing(); connectionLock.waitUntilNothingAccessing();
// Transaction queue can't be force-closed before all connections have terminated.
if (!transactionQueueClosed) forceCloseTransactionExecutor();
if (connection != null) { if (connection != null) {
MiscUtils.close(connection); MiscUtils.close(connection);
} }

View File

@ -225,6 +225,26 @@ public class PlayerCountQueries {
}; };
} }
public static Query<Integer> averageUniquePlayerCount(long after, long before, long timeZoneOffset) {
return database -> {
Sql sql = database.getSql();
String selectUniquePlayersPerDay = SELECT +
sql.dateToEpochSecond(sql.dateToDayStamp(sql.epochSecondToDate('(' + SessionsTable.SESSION_START + "+?)/1000"))) +
"*1000 as date," +
"COUNT(DISTINCT " + SessionsTable.USER_ID + ") as " + PLAYER_COUNT +
FROM + SessionsTable.TABLE_NAME +
WHERE + SessionsTable.SESSION_END + "<=?" +
AND + SessionsTable.SESSION_START + ">=?" +
GROUP_BY + "date";
String selectAverage = SELECT + "AVG(" + PLAYER_COUNT + ") as average" + FROM + '(' + selectUniquePlayersPerDay + ") q1";
return database.queryOptional(selectAverage,
set -> (int) set.getDouble("average"),
timeZoneOffset, before, after)
.orElse(0);
};
}
public static Query<Integer> newPlayerCount(long after, long before, ServerUUID serverUUID) { public static Query<Integer> newPlayerCount(long after, long before, ServerUUID serverUUID) {
String sql = SELECT + "COUNT(1) as " + PLAYER_COUNT + String sql = SELECT + "COUNT(1) as " + PLAYER_COUNT +
FROM + UserInfoTable.TABLE_NAME + FROM + UserInfoTable.TABLE_NAME +

View File

@ -40,7 +40,7 @@ public class TopListQueries {
"SUM(" + SessionsTable.SESSION_END + '-' + SessionsTable.SESSION_START + ") as playtime" + "SUM(" + SessionsTable.SESSION_END + '-' + SessionsTable.SESSION_START + ") as playtime" +
FROM + SessionsTable.TABLE_NAME + " s" + FROM + SessionsTable.TABLE_NAME + " s" +
INNER_JOIN + UsersTable.TABLE_NAME + " u on u." + UsersTable.ID + "=s." + SessionsTable.USER_ID + INNER_JOIN + UsersTable.TABLE_NAME + " u on u." + UsersTable.ID + "=s." + SessionsTable.USER_ID +
WHERE + SessionsTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID + WHERE + "(? IS NULL OR " + SessionsTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID + ')' +
AND + SessionsTable.SESSION_START + ">?" + AND + SessionsTable.SESSION_START + ">?" +
AND + SessionsTable.SESSION_END + "<?" + AND + SessionsTable.SESSION_END + "<?" +
GROUP_BY + UsersTable.USER_NAME + GROUP_BY + UsersTable.USER_NAME +
@ -49,7 +49,7 @@ public class TopListQueries {
OFFSET + "?"; OFFSET + "?";
return db -> db.queryOptional(sql, set -> new TopListEntry<>(set.getString(UsersTable.USER_NAME), set.getLong("playtime")), return db -> db.queryOptional(sql, set -> new TopListEntry<>(set.getString(UsersTable.USER_NAME), set.getLong("playtime")),
serverUUID, after, before, n); serverUUID, serverUUID, after, before, n);
} }
public static Query<Optional<TopListEntry<Long>>> fetchNthTop10ActivePlaytimePlayerOn(ServerUUID serverUUID, int n, long after, long before) { public static Query<Optional<TopListEntry<Long>>> fetchNthTop10ActivePlaytimePlayerOn(ServerUUID serverUUID, int n, long after, long before) {
@ -58,7 +58,7 @@ public class TopListQueries {
"SUM(" + SessionsTable.SESSION_END + '-' + SessionsTable.SESSION_START + '-' + SessionsTable.AFK_TIME + ") as active_playtime" + "SUM(" + SessionsTable.SESSION_END + '-' + SessionsTable.SESSION_START + '-' + SessionsTable.AFK_TIME + ") as active_playtime" +
FROM + SessionsTable.TABLE_NAME + " s" + FROM + SessionsTable.TABLE_NAME + " s" +
INNER_JOIN + UsersTable.TABLE_NAME + " u on u." + UsersTable.ID + "=s." + SessionsTable.USER_ID + INNER_JOIN + UsersTable.TABLE_NAME + " u on u." + UsersTable.ID + "=s." + SessionsTable.USER_ID +
WHERE + SessionsTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID + WHERE + "(? IS NULL OR " + SessionsTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID + ')' +
AND + SessionsTable.SESSION_START + ">?" + AND + SessionsTable.SESSION_START + ">?" +
AND + SessionsTable.SESSION_END + "<?" + AND + SessionsTable.SESSION_END + "<?" +
GROUP_BY + UsersTable.USER_NAME + GROUP_BY + UsersTable.USER_NAME +
@ -67,7 +67,7 @@ public class TopListQueries {
OFFSET + "?"; OFFSET + "?";
return db -> db.queryOptional(sql, set -> new TopListEntry<>(set.getString(UsersTable.USER_NAME), set.getLong("active_playtime")), return db -> db.queryOptional(sql, set -> new TopListEntry<>(set.getString(UsersTable.USER_NAME), set.getLong("active_playtime")),
serverUUID, after, before, n); serverUUID, serverUUID, after, before, n);
} }
public static Query<Optional<TopListEntry<Long>>> fetchNthTop10PlayerKillCountOn(ServerUUID serverUUID, int n, long after, long before) { public static Query<Optional<TopListEntry<Long>>> fetchNthTop10PlayerKillCountOn(ServerUUID serverUUID, int n, long after, long before) {

View File

@ -0,0 +1,63 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.storage.database.queries.objects;
import com.djrapitops.plan.delivery.domain.datatransfer.AllowlistBounce;
import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.storage.database.queries.Query;
import com.djrapitops.plan.storage.database.sql.tables.AllowlistBounceTable;
import com.djrapitops.plan.storage.database.sql.tables.ServerTable;
import org.intellij.lang.annotations.Language;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.UUID;
import static com.djrapitops.plan.storage.database.sql.building.Sql.*;
/**
* Query against {@link AllowlistBounceTable}.
*
* @author AuroraLS3
*/
public class AllowlistQueries {
private AllowlistQueries() {
/* Static method class */
}
public static Query<List<AllowlistBounce>> getBounces(ServerUUID serverUUID) {
@Language("SQL") String sql = SELECT +
AllowlistBounceTable.UUID + ',' +
AllowlistBounceTable.USER_NAME + ',' +
AllowlistBounceTable.TIMES + ',' +
AllowlistBounceTable.LAST_BOUNCE +
FROM + AllowlistBounceTable.TABLE_NAME +
WHERE + AllowlistBounceTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID;
return db -> db.queryList(sql, AllowlistQueries::extract, serverUUID);
}
private static AllowlistBounce extract(ResultSet set) throws SQLException {
return new AllowlistBounce(
UUID.fromString(set.getString(AllowlistBounceTable.UUID)),
set.getString(AllowlistBounceTable.USER_NAME),
set.getInt(AllowlistBounceTable.TIMES),
set.getLong(AllowlistBounceTable.LAST_BOUNCE)
);
}
}

View File

@ -28,6 +28,8 @@ import com.djrapitops.plan.storage.database.sql.tables.ServerTable;
import com.djrapitops.plan.storage.database.sql.tables.SessionsTable; import com.djrapitops.plan.storage.database.sql.tables.SessionsTable;
import com.djrapitops.plan.storage.database.sql.tables.UsersTable; import com.djrapitops.plan.storage.database.sql.tables.UsersTable;
import com.djrapitops.plan.utilities.dev.Untrusted; import com.djrapitops.plan.utilities.dev.Untrusted;
import org.apache.commons.text.TextStringBuilder;
import org.jetbrains.annotations.VisibleForTesting;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
@ -43,6 +45,7 @@ public class JoinAddressQueries {
/* Static method class */ /* Static method class */
} }
@VisibleForTesting
public static Query<Map<String, Integer>> latestJoinAddresses() { public static Query<Map<String, Integer>> latestJoinAddresses() {
String selectLatestJoinAddresses = SELECT + String selectLatestJoinAddresses = SELECT +
"COUNT(1) as total," + "COUNT(1) as total," +
@ -66,6 +69,7 @@ public class JoinAddressQueries {
joinAddresses.put(UUID.fromString(set.getString(UsersTable.USER_UUID)), set.getString(JoinAddressTable.JOIN_ADDRESS)); joinAddresses.put(UUID.fromString(set.getString(UsersTable.USER_UUID)), set.getString(JoinAddressTable.JOIN_ADDRESS));
} }
@VisibleForTesting
public static Query<Map<String, Integer>> latestJoinAddresses(ServerUUID serverUUID) { public static Query<Map<String, Integer>> latestJoinAddresses(ServerUUID serverUUID) {
String selectLatestSessionStarts = SELECT + SessionsTable.USER_ID + ",MAX(" + SessionsTable.SESSION_START + ") as max_start" + String selectLatestSessionStarts = SELECT + SessionsTable.USER_ID + ",MAX(" + SessionsTable.SESSION_START + ") as max_start" +
FROM + SessionsTable.TABLE_NAME + " max_s" + FROM + SessionsTable.TABLE_NAME + " max_s" +
@ -141,6 +145,28 @@ public class JoinAddressQueries {
}; };
} }
public static QueryStatement<List<String>> allJoinAddresses(ServerUUID serverUUID) {
String sql = SELECT + DISTINCT + JoinAddressTable.JOIN_ADDRESS +
FROM + JoinAddressTable.TABLE_NAME + " j" +
INNER_JOIN + SessionsTable.TABLE_NAME + " s ON s." + SessionsTable.JOIN_ADDRESS_ID + "=j." + JoinAddressTable.ID +
WHERE + SessionsTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID +
ORDER_BY + JoinAddressTable.JOIN_ADDRESS + " ASC";
return new QueryStatement<>(sql, 100) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setString(1, serverUUID.toString());
}
@Override
public List<String> processResults(ResultSet set) throws SQLException {
List<String> joinAddresses = new ArrayList<>();
while (set.next()) joinAddresses.add(set.getString(JoinAddressTable.JOIN_ADDRESS));
return joinAddresses;
}
};
}
public static Query<List<String>> uniqueJoinAddresses() { public static Query<List<String>> uniqueJoinAddresses() {
return db -> { return db -> {
List<String> addresses = db.query(allJoinAddresses()); List<String> addresses = db.query(allJoinAddresses());
@ -151,6 +177,16 @@ public class JoinAddressQueries {
}; };
} }
public static Query<List<String>> uniqueJoinAddresses(ServerUUID serverUUID) {
return db -> {
List<String> addresses = db.query(allJoinAddresses(serverUUID));
if (!addresses.contains(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP)) {
addresses.add(JoinAddressTable.DEFAULT_VALUE_FOR_LOOKUP);
}
return addresses;
};
}
public static Query<Set<Integer>> userIdsOfPlayersWithJoinAddresses(@Untrusted List<String> joinAddresses) { public static Query<Set<Integer>> userIdsOfPlayersWithJoinAddresses(@Untrusted List<String> joinAddresses) {
String sql = SELECT + DISTINCT + SessionsTable.USER_ID + String sql = SELECT + DISTINCT + SessionsTable.USER_ID +
FROM + JoinAddressTable.TABLE_NAME + " j" + FROM + JoinAddressTable.TABLE_NAME + " j" +
@ -162,21 +198,27 @@ public class JoinAddressQueries {
return db -> db.querySet(sql, RowExtractors.getInt(SessionsTable.USER_ID), joinAddresses.toArray()); return db -> db.querySet(sql, RowExtractors.getInt(SessionsTable.USER_ID), joinAddresses.toArray());
} }
public static Query<List<DateObj<Map<String, Integer>>>> joinAddressesPerDay(ServerUUID serverUUID, long timezoneOffset, long after, long before) { public static Query<List<DateObj<Map<String, Integer>>>> joinAddressesPerDay(ServerUUID serverUUID, long timezoneOffset, long after, long before, @Untrusted List<String> addressFilter) {
return db -> { return db -> {
Sql sql = db.getSql(); Sql sql = db.getSql();
List<Integer> ids = db.query(joinAddressIds(addressFilter));
if (ids != null && ids.isEmpty()) return List.of();
String selectAddresses = SELECT + String selectAddresses = SELECT +
sql.dateToEpochSecond(sql.dateToDayStamp(sql.epochSecondToDate('(' + SessionsTable.SESSION_START + "+?)/1000"))) + sql.dateToEpochSecond(sql.dateToDayStamp(sql.epochSecondToDate('(' + SessionsTable.SESSION_START + "+?)/1000"))) +
"*1000 as date," + "*1000 as date," +
JoinAddressTable.JOIN_ADDRESS + JoinAddressTable.JOIN_ADDRESS + ',' +
SessionsTable.USER_ID +
", COUNT(1) as count" + ", COUNT(1) as count" +
FROM + SessionsTable.TABLE_NAME + " s" + FROM + SessionsTable.TABLE_NAME + " s" +
LEFT_JOIN + JoinAddressTable.TABLE_NAME + " j on s." + SessionsTable.JOIN_ADDRESS_ID + "=j." + JoinAddressTable.ID + LEFT_JOIN + JoinAddressTable.TABLE_NAME + " j on s." + SessionsTable.JOIN_ADDRESS_ID + "=j." + JoinAddressTable.ID +
WHERE + SessionsTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID + WHERE + SessionsTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID +
AND + SessionsTable.SESSION_START + ">?" + AND + SessionsTable.SESSION_START + ">?" +
AND + SessionsTable.SESSION_START + "<=?" + AND + SessionsTable.SESSION_START + "<=?" +
GROUP_BY + "date,j." + JoinAddressTable.JOIN_ADDRESS; (ids == null ? "" : AND + "j." + JoinAddressTable.ID +
" IN (" + new TextStringBuilder().appendWithSeparators(ids, ",").build() + ")") +
GROUP_BY + "date,j." + JoinAddressTable.JOIN_ADDRESS + ',' + SessionsTable.USER_ID;
return db.query(new QueryStatement<>(selectAddresses, 1000) { return db.query(new QueryStatement<>(selectAddresses, 1000) {
@Override @Override
@ -193,9 +235,9 @@ public class JoinAddressQueries {
while (set.next()) { while (set.next()) {
long date = set.getLong("date"); long date = set.getLong("date");
String joinAddress = set.getString(JoinAddressTable.JOIN_ADDRESS); String joinAddress = set.getString(JoinAddressTable.JOIN_ADDRESS);
int count = set.getInt("count");
Map<String, Integer> joinAddresses = addressesByDate.computeIfAbsent(date, k -> new TreeMap<>()); Map<String, Integer> joinAddresses = addressesByDate.computeIfAbsent(date, k -> new TreeMap<>());
joinAddresses.put(joinAddress, count); // We ignore the count and get the number of players instead of sessions
joinAddresses.compute(joinAddress, (key, oldValue) -> oldValue != null ? oldValue + 1 : 1);
} }
return addressesByDate.entrySet() return addressesByDate.entrySet()
@ -206,20 +248,37 @@ public class JoinAddressQueries {
}; };
} }
public static Query<List<DateObj<Map<String, Integer>>>> joinAddressesPerDay(long timezoneOffset, long after, long before) { public static Query<List<Integer>> joinAddressIds(@Untrusted List<String> addresses) {
return db -> {
if (addresses.isEmpty()) return null;
String selectJoinAddressIds = SELECT + JoinAddressTable.ID +
FROM + JoinAddressTable.TABLE_NAME +
WHERE + JoinAddressTable.JOIN_ADDRESS + " IN (" + Sql.nParameters(addresses.size()) + ")";
return db.queryList(selectJoinAddressIds, set -> set.getInt(JoinAddressTable.ID), addresses);
};
}
public static Query<List<DateObj<Map<String, Integer>>>> joinAddressesPerDay(long timezoneOffset, long after, long before, @Untrusted List<String> addressFilter) {
return db -> { return db -> {
Sql sql = db.getSql(); Sql sql = db.getSql();
List<Integer> ids = db.query(joinAddressIds(addressFilter));
if (ids != null && ids.isEmpty()) return List.of();
String selectAddresses = SELECT + String selectAddresses = SELECT +
sql.dateToEpochSecond(sql.dateToDayStamp(sql.epochSecondToDate('(' + SessionsTable.SESSION_START + "+?)/1000"))) + sql.dateToEpochSecond(sql.dateToDayStamp(sql.epochSecondToDate('(' + SessionsTable.SESSION_START + "+?)/1000"))) +
"*1000 as date," + "*1000 as date," +
JoinAddressTable.JOIN_ADDRESS + JoinAddressTable.JOIN_ADDRESS + ',' +
SessionsTable.USER_ID +
", COUNT(1) as count" + ", COUNT(1) as count" +
FROM + SessionsTable.TABLE_NAME + " s" + FROM + SessionsTable.TABLE_NAME + " s" +
LEFT_JOIN + JoinAddressTable.TABLE_NAME + " j on s." + SessionsTable.JOIN_ADDRESS_ID + "=j." + JoinAddressTable.ID + LEFT_JOIN + JoinAddressTable.TABLE_NAME + " j on s." + SessionsTable.JOIN_ADDRESS_ID + "=j." + JoinAddressTable.ID +
WHERE + SessionsTable.SESSION_START + ">?" + WHERE + SessionsTable.SESSION_START + ">?" +
AND + SessionsTable.SESSION_START + "<=?" + AND + SessionsTable.SESSION_START + "<=?" +
GROUP_BY + "date,j." + JoinAddressTable.JOIN_ADDRESS; (ids == null ? "" : AND + "j." + JoinAddressTable.ID +
" IN (" + new TextStringBuilder().appendWithSeparators(ids, ",").build() + ")") +
GROUP_BY + "date,j." + JoinAddressTable.JOIN_ADDRESS + ',' + SessionsTable.USER_ID;
return db.query(new QueryStatement<>(selectAddresses, 1000) { return db.query(new QueryStatement<>(selectAddresses, 1000) {
@Override @Override
@ -235,9 +294,9 @@ public class JoinAddressQueries {
while (set.next()) { while (set.next()) {
long date = set.getLong("date"); long date = set.getLong("date");
String joinAddress = set.getString(JoinAddressTable.JOIN_ADDRESS); String joinAddress = set.getString(JoinAddressTable.JOIN_ADDRESS);
int count = set.getInt("count");
Map<String, Integer> joinAddresses = addressesByDate.computeIfAbsent(date, k -> new TreeMap<>()); Map<String, Integer> joinAddresses = addressesByDate.computeIfAbsent(date, k -> new TreeMap<>());
joinAddresses.put(joinAddress, count); // We ignore the count and get the number of players instead of sessions
joinAddresses.compute(joinAddress, (key, oldValue) -> oldValue != null ? oldValue + 1 : 1);
} }
return addressesByDate.entrySet() return addressesByDate.entrySet()

View File

@ -260,4 +260,12 @@ public class PingQueries {
} }
}; };
} }
public static Query<Double> averagePing(long after, long before) {
String sql = SELECT + "AVG(" + PingTable.AVG_PING + ") as average" + FROM + PingTable.TABLE_NAME +
WHERE + PingTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID +
AND + PingTable.DATE + ">=?" +
AND + PingTable.DATE + "<=?";
return db -> db.queryOptional(sql, set -> set.getDouble("average"), after, before).orElse(-1.0);
}
} }

View File

@ -917,6 +917,16 @@ public class SessionQueries {
}; };
} }
public static Query<Long> activePlaytime(long after, long before) {
String sql = SELECT + "SUM(" + SessionsTable.SESSION_END + '-' + SessionsTable.SESSION_START + '-' + SessionsTable.AFK_TIME +
") as playtime" +
FROM + SessionsTable.TABLE_NAME +
WHERE + SessionsTable.SESSION_END + ">=?" +
AND + SessionsTable.SESSION_START + "<=?";
return db -> db.queryOptional(sql, set -> set.getLong("playtime"), after, before)
.orElse(0L);
}
public static Query<Set<Integer>> userIdsOfPlayedBetween(long after, long before, List<ServerUUID> serverUUIDs) { public static Query<Set<Integer>> userIdsOfPlayedBetween(long after, long before, List<ServerUUID> serverUUIDs) {
String selectServerIds = SELECT + ServerTable.ID + String selectServerIds = SELECT + ServerTable.ID +
FROM + ServerTable.TABLE_NAME + FROM + ServerTable.TABLE_NAME +
@ -1004,4 +1014,16 @@ public class SessionQueries {
} }
}; };
} }
public static Query<Map<UUID, Long>> lastSeen(ServerUUID serverUUID) {
String sql = SELECT + UsersTable.USER_UUID + ", MAX(" + SessionsTable.SESSION_END + ") as last_seen" +
FROM + SessionsTable.TABLE_NAME + " s" +
INNER_JOIN + UsersTable.TABLE_NAME + " u ON u." + UsersTable.ID + "=s." + SessionsTable.USER_ID +
WHERE + SessionsTable.SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID +
GROUP_BY + UsersTable.USER_UUID;
return db -> db.queryMap(sql, (set, to) -> to.put(
UUID.fromString(set.getString(UsersTable.USER_UUID)),
set.getLong("last_seen")
), serverUUID);
}
} }

View File

@ -107,6 +107,8 @@ public class QueryTablePlayersQuery implements Query<List<TablePlayer>> {
"MIN(p." + PingTable.MIN_PING + ") as " + PingTable.MIN_PING + "MIN(p." + PingTable.MIN_PING + ") as " + PingTable.MIN_PING +
FROM + PingTable.TABLE_NAME + " p" + FROM + PingTable.TABLE_NAME + " p" +
WHERE + "p." + PingTable.USER_ID + userIdsInSet + WHERE + "p." + PingTable.USER_ID + userIdsInSet +
AND + "p." + PingTable.DATE + ">=" + afterDate +
AND + "p." + PingTable.DATE + "<=" + beforeDate +
(serverUUIDs.isEmpty() ? "" : AND + "p." + PingTable.SERVER_ID + " IN (" + selectServerIds + ")") + (serverUUIDs.isEmpty() ? "" : AND + "p." + PingTable.SERVER_ID + " IN (" + selectServerIds + ")") +
GROUP_BY + "p." + PingTable.USER_ID; GROUP_BY + "p." + PingTable.USER_ID;

View File

@ -16,6 +16,7 @@
*/ */
package com.djrapitops.plan.storage.database.sql.building; package com.djrapitops.plan.storage.database.sql.building;
import com.djrapitops.plan.storage.database.DBType;
import org.apache.commons.text.TextStringBuilder; import org.apache.commons.text.TextStringBuilder;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
@ -96,6 +97,15 @@ public abstract class Sql {
} }
} }
public static String concat(DBType dbType, String one, String two) {
if (dbType == DBType.MYSQL) {
return "CONCAT(" + one + ',' + two + ")";
} else if (dbType == DBType.SQLITE) {
return one + " || " + two;
}
return one + two;
}
public abstract String epochSecondToDate(String sql); public abstract String epochSecondToDate(String sql);
public abstract String dateToEpochSecond(String sql); public abstract String dateToEpochSecond(String sql);
@ -108,6 +118,8 @@ public abstract class Sql {
public abstract String dateToHour(String sql); public abstract String dateToHour(String sql);
public abstract String insertOrIgnore();
// https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html // https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html
public static class MySQL extends Sql { public static class MySQL extends Sql {
@ -140,6 +152,11 @@ public abstract class Sql {
public String dateToHour(String sql) { public String dateToHour(String sql) {
return "HOUR(" + sql + ") % 24"; return "HOUR(" + sql + ") % 24";
} }
@Override
public String insertOrIgnore() {
return "INSERT IGNORE INTO ";
}
} }
// https://sqlite.org/lang_datefunc.html // https://sqlite.org/lang_datefunc.html
@ -174,5 +191,10 @@ public abstract class Sql {
public String dateToHour(String sql) { public String dateToHour(String sql) {
return "strftime('%H'," + sql + ')'; return "strftime('%H'," + sql + ')';
} }
@Override
public String insertOrIgnore() {
return "INSERT OR IGNORE INTO ";
}
} }
} }

View File

@ -0,0 +1,70 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.storage.database.sql.tables;
import com.djrapitops.plan.storage.database.DBType;
import com.djrapitops.plan.storage.database.sql.building.CreateTableBuilder;
import com.djrapitops.plan.storage.database.sql.building.Sql;
import org.intellij.lang.annotations.Language;
/**
* Represents plan_allowlist_bounce table.
*
* @author AuroraLS3
*/
public class AllowlistBounceTable {
public static final String TABLE_NAME = "plan_allowlist_bounce";
public static final String ID = "id";
public static final String UUID = "uuid";
public static final String USER_NAME = "name";
public static final String SERVER_ID = "server_id";
public static final String TIMES = "times";
public static final String LAST_BOUNCE = "last_bounce";
@Language("SQL")
public static final String INSERT_STATEMENT = "INSERT INTO " + TABLE_NAME + " (" +
UUID + ',' +
USER_NAME + ',' +
SERVER_ID + ',' +
TIMES + ',' +
LAST_BOUNCE +
") VALUES (?,?," + ServerTable.SELECT_SERVER_ID + ",?,?)";
@Language("SQL")
public static final String INCREMENT_TIMES_STATEMENT = "UPDATE " + TABLE_NAME +
" SET " + TIMES + "=" + TIMES + "+1, " + LAST_BOUNCE + "=?" +
" WHERE " + UUID + "=?" +
" AND " + SERVER_ID + "=" + ServerTable.SELECT_SERVER_ID;
private AllowlistBounceTable() {
/* Static information class */
}
public static String createTableSQL(DBType dbType) {
return CreateTableBuilder.create(TABLE_NAME, dbType)
.column(ID, Sql.INT).primaryKey()
.column(UUID, Sql.varchar(36)).notNull().unique()
.column(USER_NAME, Sql.varchar(36)).notNull()
.column(SERVER_ID, Sql.INT).notNull()
.column(TIMES, Sql.INT).notNull().defaultValue("0")
.column(LAST_BOUNCE, Sql.LONG).notNull()
.foreignKey(SERVER_ID, ServerTable.TABLE_NAME, ServerTable.ID)
.toString();
}
}

View File

@ -24,6 +24,7 @@ import com.djrapitops.plan.storage.database.sql.building.Insert;
import com.djrapitops.plan.storage.database.sql.building.Sql; import com.djrapitops.plan.storage.database.sql.building.Sql;
import com.djrapitops.plan.storage.database.sql.building.Update; import com.djrapitops.plan.storage.database.sql.building.Update;
import org.apache.commons.text.TextStringBuilder; import org.apache.commons.text.TextStringBuilder;
import org.intellij.lang.annotations.Language;
import java.util.Collection; import java.util.Collection;
@ -61,6 +62,7 @@ public class ServerTable {
.where(SERVER_UUID + "=?") .where(SERVER_UUID + "=?")
.toString(); .toString();
@Language("SQL")
public static final String SELECT_SERVER_ID = public static final String SELECT_SERVER_ID =
'(' + SELECT + TABLE_NAME + '.' + ID + '(' + SELECT + TABLE_NAME + '.' + ID +
FROM + TABLE_NAME + FROM + TABLE_NAME +

View File

@ -41,6 +41,10 @@ public class WebPermissionTable {
/* Static information class */ /* Static information class */
} }
public static String safeInsertSQL(DBType dbType) {
return dbType.getSql().insertOrIgnore() + TABLE_NAME + " (" + PERMISSION + ") VALUES (?)";
}
public static String createTableSQL(DBType dbType) { public static String createTableSQL(DBType dbType) {
return CreateTableBuilder.create(TABLE_NAME, dbType) return CreateTableBuilder.create(TABLE_NAME, dbType)
.column(ID, Sql.INT).primaryKey() .column(ID, Sql.INT).primaryKey()

View File

@ -43,6 +43,7 @@ public class RemoveEverythingTransaction extends Patch {
clearTable(WorldTimesTable.TABLE_NAME); clearTable(WorldTimesTable.TABLE_NAME);
clearTable(SessionsTable.TABLE_NAME); clearTable(SessionsTable.TABLE_NAME);
clearTable(JoinAddressTable.TABLE_NAME); clearTable(JoinAddressTable.TABLE_NAME);
clearTable(AllowlistBounceTable.TABLE_NAME);
clearTable(WorldTable.TABLE_NAME); clearTable(WorldTable.TABLE_NAME);
clearTable(PingTable.TABLE_NAME); clearTable(PingTable.TABLE_NAME);
clearTable(UserInfoTable.TABLE_NAME); clearTable(UserInfoTable.TABLE_NAME);

View File

@ -0,0 +1,43 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.storage.database.transactions.commands;
import com.djrapitops.plan.storage.database.sql.tables.webuser.SecurityTable;
import com.djrapitops.plan.storage.database.sql.tables.webuser.WebGroupTable;
import com.djrapitops.plan.storage.database.sql.tables.webuser.WebGroupToPermissionTable;
import com.djrapitops.plan.storage.database.sql.tables.webuser.WebUserPreferencesTable;
import com.djrapitops.plan.storage.database.transactions.Transaction;
/**
* Transaction that removes all web groups from the database.
*
* @author AuroraLS3
*/
public class RemoveWebGroupsTransaction extends Transaction {
@Override
protected void performOperations() {
clearTable(WebUserPreferencesTable.TABLE_NAME);
clearTable(SecurityTable.TABLE_NAME);
clearTable(WebGroupToPermissionTable.TABLE_NAME);
clearTable(WebGroupTable.TABLE_NAME);
}
private void clearTable(String tableName) {
execute("DELETE FROM " + tableName);
}
}

View File

@ -0,0 +1,72 @@
/*
* This file is part of Player Analytics (Plan).
*
* Plan is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License v3 as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Plan is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Plan. If not, see <https://www.gnu.org/licenses/>.
*/
package com.djrapitops.plan.storage.database.transactions.events;
import com.djrapitops.plan.identification.ServerUUID;
import com.djrapitops.plan.storage.database.sql.tables.AllowlistBounceTable;
import com.djrapitops.plan.storage.database.transactions.ExecStatement;
import com.djrapitops.plan.storage.database.transactions.Transaction;
import com.djrapitops.plan.utilities.dev.Untrusted;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.UUID;
/**
* Stores a bounced allowlist login.
*
* @author AuroraLS3
*/
public class StoreAllowlistBounceTransaction extends Transaction {
private final UUID playerUUID;
@Untrusted
private final String playerName;
private final ServerUUID serverUUID;
private final long time;
public StoreAllowlistBounceTransaction(UUID playerUUID, @Untrusted String playerName, ServerUUID serverUUID, long time) {
this.playerUUID = playerUUID;
this.playerName = playerName;
this.serverUUID = serverUUID;
this.time = time;
}
@Override
protected void performOperations() {
boolean updated = execute(new ExecStatement(AllowlistBounceTable.INCREMENT_TIMES_STATEMENT) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setLong(1, time);
statement.setString(2, playerUUID.toString());
statement.setString(3, serverUUID.toString());
}
});
if (!updated) {
execute(new ExecStatement(AllowlistBounceTable.INSERT_STATEMENT) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setString(1, playerUUID.toString());
statement.setString(2, playerName);
statement.setString(3, serverUUID.toString());
statement.setInt(4, 1);
statement.setLong(5, time);
}
});
}
}
}

View File

@ -16,49 +16,33 @@
*/ */
package com.djrapitops.plan.storage.database.transactions.events; package com.djrapitops.plan.storage.database.transactions.events;
import com.djrapitops.plan.delivery.web.resolver.Response;
import com.djrapitops.plan.delivery.web.resolver.request.Request; import com.djrapitops.plan.delivery.web.resolver.request.Request;
import com.djrapitops.plan.delivery.webserver.configuration.WebserverConfiguration;
import com.djrapitops.plan.delivery.webserver.http.InternalRequest; import com.djrapitops.plan.delivery.webserver.http.InternalRequest;
import com.djrapitops.plan.storage.database.sql.tables.AccessLogTable; import com.djrapitops.plan.storage.database.sql.tables.AccessLogTable;
import com.djrapitops.plan.storage.database.transactions.ExecStatement; import com.djrapitops.plan.storage.database.transactions.ExecStatement;
import com.djrapitops.plan.storage.database.transactions.Transaction; import com.djrapitops.plan.storage.database.transactions.ThrowawayTransaction;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.SQLException; import java.sql.SQLException;
public class StoreRequestTransaction extends Transaction { public class StoreRequestTransaction extends ThrowawayTransaction {
private final WebserverConfiguration webserverConfiguration; private final long timestamp;
private final String accessAddress;
private final String method;
private final String url;
private final int responseCode;
private final InternalRequest internalRequest; public StoreRequestTransaction(long timestamp, String accessAddress, String method, String url, int responseCode) {
private final Request request; // can be null this.timestamp = timestamp;
private final Response response; this.accessAddress = accessAddress;
this.method = method;
public StoreRequestTransaction(WebserverConfiguration webserverConfiguration, InternalRequest internalRequest, Request request, Response response) { this.url = url;
this.webserverConfiguration = webserverConfiguration; this.responseCode = responseCode;
this.internalRequest = internalRequest;
this.request = request;
this.response = response;
} }
@Override public static String getTruncatedURI(Request request, InternalRequest internalRequest) {
protected void performOperations() {
execute(new ExecStatement(AccessLogTable.INSERT_NO_USER) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setLong(1, internalRequest.getTimestamp());
statement.setString(2, internalRequest.getAccessAddress(webserverConfiguration));
String method = internalRequest.getMethod();
statement.setString(3, method != null ? method : "?");
statement.setString(4, getTruncatedURI());
statement.setInt(5, response.getCode());
}
});
}
private String getTruncatedURI() {
String uri = request != null ? request.getPath().asString() + request.getQuery().asString() String uri = request != null ? request.getPath().asString() + request.getQuery().asString()
: internalRequest.getRequestedURIString(); : internalRequest.getRequestedURIString();
if (uri == null) { if (uri == null) {
@ -66,4 +50,18 @@ public class StoreRequestTransaction extends Transaction {
} }
return StringUtils.truncate(uri, 65000); return StringUtils.truncate(uri, 65000);
} }
@Override
protected void performOperations() {
execute(new ExecStatement(AccessLogTable.INSERT_NO_USER) {
@Override
public void prepare(PreparedStatement statement) throws SQLException {
statement.setLong(1, timestamp);
statement.setString(2, StringUtils.truncate(accessAddress, 45));
statement.setString(3, method);
statement.setString(4, url);
statement.setInt(5, responseCode);
}
});
}
} }

View File

@ -59,6 +59,7 @@ public class CreateTablesTransaction extends OperationCriticalTransaction {
executeOther(new SecurityTableIdPatch()); executeOther(new SecurityTableIdPatch());
execute(WebUserPreferencesTable.createTableSQL(dbType)); execute(WebUserPreferencesTable.createTableSQL(dbType));
execute(PluginVersionTable.createTableSQL(dbType)); execute(PluginVersionTable.createTableSQL(dbType));
execute(AllowlistBounceTable.createTableSQL(dbType));
// DataExtension tables // DataExtension tables
execute(ExtensionIconTable.createTableSQL(dbType)); execute(ExtensionIconTable.createTableSQL(dbType));

View File

@ -51,6 +51,8 @@ public class CorrectWrongCharacterEncodingPatch extends Patch {
if (dbType != DBType.MYSQL) return true; if (dbType != DBType.MYSQL) return true;
correctionSqlQueries = query(getBadTableCorrectionQueries()); correctionSqlQueries = query(getBadTableCorrectionQueries());
// Fix for MariaDB mysql.user table being a view
correctionSqlQueries.removeIf(sql -> sql.startsWith("ALTER TABLE `user`"));
return correctionSqlQueries.isEmpty(); return correctionSqlQueries.isEmpty();
} }

View File

@ -17,7 +17,7 @@
package com.djrapitops.plan.storage.database.transactions.patches; package com.djrapitops.plan.storage.database.transactions.patches;
import com.djrapitops.plan.exceptions.database.DBOpException; import com.djrapitops.plan.exceptions.database.DBOpException;
import com.djrapitops.plan.storage.database.DBType; import com.djrapitops.plan.storage.database.sql.building.Sql;
import com.djrapitops.plan.storage.database.sql.tables.webuser.SecurityTable; import com.djrapitops.plan.storage.database.sql.tables.webuser.SecurityTable;
import com.djrapitops.plan.storage.database.sql.tables.webuser.WebGroupTable; import com.djrapitops.plan.storage.database.sql.tables.webuser.WebGroupTable;
@ -71,7 +71,7 @@ public class SecurityTableGroupPatch extends Patch {
SecurityTable.USERNAME + ',' + SecurityTable.USERNAME + ',' +
SecurityTable.LINKED_TO + ',' + SecurityTable.LINKED_TO + ',' +
SecurityTable.SALT_PASSWORD_HASH + ',' + SecurityTable.SALT_PASSWORD_HASH + ',' +
"(" + SELECT + WebGroupTable.ID + FROM + WebGroupTable.TABLE_NAME + WHERE + WebGroupTable.NAME + "=" + (dbType == DBType.SQLITE ? "'legacy_level_' || permission_level" : "CONCAT('legacy_level_', permission_level)") + ")" + "(" + SELECT + WebGroupTable.ID + FROM + WebGroupTable.TABLE_NAME + WHERE + WebGroupTable.NAME + "=" + Sql.concat(dbType, "'legacy_level_'", "permission_level") + ")" +
FROM + tempTableName FROM + tempTableName
); );

View File

@ -39,7 +39,7 @@ public class UpdateWebPermissionsPatch extends Patch {
@Override @Override
public boolean hasBeenApplied() { public boolean hasBeenApplied() {
List<String> defaultPermissions = Arrays.stream(WebPermission.values()) List<String> defaultPermissions = Arrays.stream(WebPermission.nonDeprecatedValues())
.map(WebPermission::getPermission) .map(WebPermission::getPermission)
.collect(Collectors.toList()); .collect(Collectors.toList());
List<String> storedPermissions = query(WebUserQueries.fetchAvailablePermissions()); List<String> storedPermissions = query(WebUserQueries.fetchAvailablePermissions());
@ -55,7 +55,11 @@ public class UpdateWebPermissionsPatch extends Patch {
@Override @Override
protected void applyPatch() { protected void applyPatch() {
execute(new ExecBatchStatement(WebPermissionTable.INSERT_STATEMENT) { storeMissing();
}
private void storeMissing() {
execute(new ExecBatchStatement(WebPermissionTable.safeInsertSQL(dbType)) {
@Override @Override
public void prepare(PreparedStatement statement) throws SQLException { public void prepare(PreparedStatement statement) throws SQLException {
for (String permission : missingPermissions) { for (String permission : missingPermissions) {

View File

@ -50,7 +50,7 @@ public class StoreMissingWebPermissionsTransaction extends Transaction {
missingPermissions.add(permission); missingPermissions.add(permission);
} }
} }
execute(new ExecBatchStatement(WebPermissionTable.INSERT_STATEMENT) { execute(new ExecBatchStatement(WebPermissionTable.safeInsertSQL(dbType)) {
@Override @Override
public void prepare(PreparedStatement statement) throws SQLException { public void prepare(PreparedStatement statement) throws SQLException {
for (String permission : missingPermissions) { for (String permission : missingPermissions) {

View File

@ -16,7 +16,7 @@
*/ */
package com.djrapitops.plan.storage.file; package com.djrapitops.plan.storage.file;
import org.apache.commons.compress.utils.IOUtils; import org.apache.commons.io.IOUtils;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;

View File

@ -18,8 +18,6 @@ package com.djrapitops.plan.storage.upkeep;
import com.djrapitops.plan.TaskSystem; import com.djrapitops.plan.TaskSystem;
import com.djrapitops.plan.exceptions.database.DBOpException; import com.djrapitops.plan.exceptions.database.DBOpException;
import com.djrapitops.plan.extension.implementation.storage.transactions.results.RemoveUnsatisfiedConditionalPlayerResultsTransaction;
import com.djrapitops.plan.extension.implementation.storage.transactions.results.RemoveUnsatisfiedConditionalServerResultsTransaction;
import com.djrapitops.plan.identification.ServerInfo; import com.djrapitops.plan.identification.ServerInfo;
import com.djrapitops.plan.query.QuerySvc; import com.djrapitops.plan.query.QuerySvc;
import com.djrapitops.plan.settings.config.PlanConfig; import com.djrapitops.plan.settings.config.PlanConfig;
@ -112,8 +110,6 @@ public class DBCleanTask extends TaskSystem.Task {
config.get(TimeSettings.DELETE_PING_DATA_AFTER) config.get(TimeSettings.DELETE_PING_DATA_AFTER)
)); ));
database.executeTransaction(new RemoveDuplicateUserInfoTransaction()); database.executeTransaction(new RemoveDuplicateUserInfoTransaction());
database.executeTransaction(new RemoveUnsatisfiedConditionalPlayerResultsTransaction());
database.executeTransaction(new RemoveUnsatisfiedConditionalServerResultsTransaction());
int removed = cleanOldPlayers(database); int removed = cleanOldPlayers(database);
if (removed > 0) { if (removed > 0) {
logger.info(locale.getString(PluginLang.DB_NOTIFY_CLEAN, removed)); logger.info(locale.getString(PluginLang.DB_NOTIFY_CLEAN, removed));

View File

@ -16,24 +16,59 @@
*/ */
package com.djrapitops.plan.utilities; package com.djrapitops.plan.utilities;
import com.djrapitops.plan.storage.database.SQLDB;
import com.djrapitops.plan.utilities.java.ThrowableUtils;
import org.jetbrains.annotations.NotNull;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
public class SemaphoreAccessCounter { public class SemaphoreAccessCounter {
private final AtomicInteger accessCounter; private final AtomicInteger accessCounter;
private final Object lockObject; private final Object lockObject;
private final Collection<String> holds = Collections.newSetFromMap(new ConcurrentHashMap<>());
public SemaphoreAccessCounter() { public SemaphoreAccessCounter() {
accessCounter = new AtomicInteger(0); accessCounter = new AtomicInteger(0);
lockObject = new Object(); lockObject = new Object();
} }
@NotNull
private static String getAccessingThing() {
boolean previousWasAccess = false;
List<StackTraceElement> accessors = new ArrayList<>();
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
StackTraceElement[] origin = SQLDB.getTransactionOrigin().get();
StackTraceElement[] callSite = ThrowableUtils.combineStackTrace(origin, stackTrace);
for (StackTraceElement e : callSite) {
if (previousWasAccess) {
accessors.add(e);
previousWasAccess = false;
}
String call = e.getClassName() + "." + e.getMethodName();
if ("com.djrapitops.plan.storage.database.SQLDB.query".equals(call)
|| "com.djrapitops.plan.storage.database.SQLDB.executeTransaction".equals(call)) {
previousWasAccess = true;
}
}
if (accessors.isEmpty()) accessors.addAll(Arrays.asList(callSite));
return accessors.toString();
}
public void enter() { public void enter() {
accessCounter.incrementAndGet(); accessCounter.incrementAndGet();
holds.add(getAccessingThing());
} }
public void exit() { public void exit() {
synchronized (lockObject) { synchronized (lockObject) {
holds.remove(getAccessingThing());
int value = accessCounter.decrementAndGet(); int value = accessCounter.decrementAndGet();
if (value == 0) { if (value == 0) {
lockObject.notifyAll(); lockObject.notifyAll();
@ -45,6 +80,7 @@ public class SemaphoreAccessCounter {
while (accessCounter.get() > 0) { while (accessCounter.get() > 0) {
synchronized (lockObject) { synchronized (lockObject) {
try { try {
logAccess();
lockObject.wait(); lockObject.wait();
} catch (InterruptedException e) { } catch (InterruptedException e) {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
@ -52,4 +88,16 @@ public class SemaphoreAccessCounter {
} }
} }
} }
private void logAccess() {
Logger logger = Logger.getLogger("Plan");
if (logger == null) logger = Logger.getGlobal();
if (logger.isLoggable(Level.INFO) && !holds.isEmpty()) {
logger.log(Level.INFO, "Waiting for these connections to finish:");
for (String hold : holds) {
logger.log(Level.INFO, hold);
}
}
}
} }

View File

@ -16,6 +16,8 @@
*/ */
package com.djrapitops.plan.utilities.java; package com.djrapitops.plan.utilities.java;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays; import java.util.Arrays;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -30,20 +32,29 @@ public class ThrowableUtils {
/* Static method class */ /* Static method class */
} }
public static void appendEntryPointToCause(Throwable throwable, Throwable originPoint) { public static void appendEntryPointToCause(Throwable throwable, StackTraceElement[] originPoint) {
Throwable cause = throwable.getCause(); Throwable cause = throwable.getCause();
while (cause.getCause() != null) { while (cause.getCause() != null) {
cause = cause.getCause(); cause = cause.getCause();
} }
cause.setStackTrace( cause.setStackTrace(
Stream.concat( combineStackTrace(originPoint, cause.getStackTrace())
Arrays.stream(cause.getStackTrace()),
Arrays.stream(originPoint.getStackTrace())
).toArray(StackTraceElement[]::new)
); );
} }
@NotNull
public static StackTraceElement[] combineStackTrace(StackTraceElement[] originPoint, StackTraceElement[] cause) {
if (originPoint == null && cause == null) return new StackTraceElement[0];
if (originPoint == null) return cause;
if (cause == null) return originPoint;
return Stream.concat(
Arrays.stream(cause),
Arrays.stream(originPoint)
).toArray(StackTraceElement[]::new);
}
public static String findCallerAfterClass(StackTraceElement[] stackTrace, Class<?> afterThis) { public static String findCallerAfterClass(StackTraceElement[] stackTrace, Class<?> afterThis) {
boolean found = false; boolean found = false;
for (StackTraceElement stackTraceElement : stackTrace) { for (StackTraceElement stackTraceElement : stackTrace) {

View File

@ -1 +0,0 @@
45VvUnNtiDHKZ+hq3vqx204q+tmLRE/koVskJLaT2+ipY8G1ThqcLZjUMuF79lYLpRIqpAt4KcY=

View File

@ -1 +0,0 @@
YEQ4eTdZPzUpUV4zcTp6NkE7XEw=

View File

@ -109,10 +109,19 @@ Data_gathering:
# Please accept the EULA to download GeoLite2 IP-Country Database # Please accept the EULA to download GeoLite2 IP-Country Database
# https://www.maxmind.com/en/geolite2/eula # https://www.maxmind.com/en/geolite2/eula
Accept_GeoLite2_EULA: false Accept_GeoLite2_EULA: false
# This can be changed to your own MaxMind URL if you have license https://dev.maxmind.com/geoip/updating-databases?lang=en#directly-downloading-databases
# e.g. https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-Country&license_key=YOUR_KEY&suffix=tar.gz
Geolocation_Download_URL: "https://geodb.playeranalytics.net/GeoLite2-Country.mmdb"
Ping: true Ping: true
Disk_space: true Disk_space: true
# Does not affect already gathered data Join_addresses:
Preserve_join_address_case: false Enabled: true
# Does not affect already gathered data
Preserve_case: false
Preserve_invalid: false
# Replaces these join addresses with unknown
Filter_out_from_data:
- "play.example.com"
# ----------------------------------------------------- # -----------------------------------------------------
# Supported time units: MILLISECONDS, SECONDS, MINUTES, HOURS, DAYS # Supported time units: MILLISECONDS, SECONDS, MINUTES, HOURS, DAYS
# ----------------------------------------------------- # -----------------------------------------------------

View File

@ -110,13 +110,22 @@ Data_gathering:
# Please accept the EULA to download GeoLite2 IP-Country Database # Please accept the EULA to download GeoLite2 IP-Country Database
# https://www.maxmind.com/en/geolite2/eula # https://www.maxmind.com/en/geolite2/eula
Accept_GeoLite2_EULA: false Accept_GeoLite2_EULA: false
# This can be changed to your own MaxMind URL if you have license https://dev.maxmind.com/geoip/updating-databases?lang=en#directly-downloading-databases
# e.g. https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-Country&license_key=YOUR_KEY&suffix=tar.gz
Geolocation_Download_URL: "https://geodb.playeranalytics.net/GeoLite2-Country.mmdb"
Ping: true Ping: true
Disk_space: true Disk_space: true
Commands: Commands:
Log_unknown: false Log_unknown: false
Log_aliases_as_main_command: true Log_aliases_as_main_command: true
# Does not affect already gathered data Join_addresses:
Preserve_join_address_case: false Enabled: true
# Does not affect already gathered data
Preserve_case: false
Preserve_invalid: false
# Replaces these join addresses with unknown
Filter_out_from_data:
- "play.example.com"
# ----------------------------------------------------- # -----------------------------------------------------
# Supported time units: MILLISECONDS, SECONDS, MINUTES, HOURS, DAYS # Supported time units: MILLISECONDS, SECONDS, MINUTES, HOURS, DAYS
# ----------------------------------------------------- # -----------------------------------------------------

View File

@ -184,7 +184,7 @@ command:
inDepth: "获得一个指向 /players page全体玩家页面 的链接,以查看玩家列表。" inDepth: "获得一个指向 /players page全体玩家页面 的链接,以查看玩家列表。"
register: register:
description: "注册一个网页用户" description: "注册一个网页用户"
inDepth: "直接使用会获得注册页面的链接。添加 --code[注册代码] 参数可以注册一个账户。" inDepth: "直接使用会获得注册页面的链接。添加 --code [注册代码] 参数可以注册一个账户。"
reload: reload:
description: "重启 Plan" description: "重启 Plan"
inDepth: "禁用然后重新启用本插件,会重新加载配置中的设置。" inDepth: "禁用然后重新启用本插件,会重新加载配置中的设置。"
@ -290,13 +290,19 @@ html:
active: "活跃" active: "活跃"
activePlaytime: "活跃时间" activePlaytime: "活跃时间"
activityIndex: "活跃指数" activityIndex: "活跃指数"
addJoinAddressGroup: "添加地址组"
addressGroup: "地址组 {{n}}"
afk: "挂机" afk: "挂机"
afkTime: "挂机时间" afkTime: "挂机时间"
all: "全部" all: "全部"
allTime: "所有时间" allTime: "所有时间"
allowed: "允许"
allowlist: "白名单"
allowlistBounces: "白名单退回"
alphabetical: "按字母顺序" alphabetical: "按字母顺序"
apply: "应用" apply: "应用"
asNumbers: "数据" asNumbers: "数据"
attempts: "尝试"
average: "平均" average: "平均"
averageActivePlaytime: "平均活跃时间" averageActivePlaytime: "平均活跃时间"
averageAfkTime: "平均挂机时间" averageAfkTime: "平均挂机时间"
@ -317,6 +323,7 @@ html:
banned: "已被封禁" banned: "已被封禁"
bestPeak: "历史最高峰值" bestPeak: "历史最高峰值"
bestPing: "最低延迟" bestPing: "最低延迟"
blocked: "阻止"
calendar: " 日历" calendar: " 日历"
comparing7days: "对比 7 天的情况" comparing7days: "对比 7 天的情况"
connectionInfo: "连接信息" connectionInfo: "连接信息"
@ -338,6 +345,7 @@ html:
duringLowTps: "持续低 TPS 时间" duringLowTps: "持续低 TPS 时间"
entities: "实体" entities: "实体"
errors: "Plan 错误日志" errors: "Plan 错误日志"
export: "导出"
exported: "数据导出时间" exported: "数据导出时间"
favoriteServer: "最喜欢的服务器" favoriteServer: "最喜欢的服务器"
firstSession: "第一次会话" firstSession: "第一次会话"
@ -429,7 +437,10 @@ html:
last24hours: "过去 24 小时" last24hours: "过去 24 小时"
last30days: "过去 30 天" last30days: "过去 30 天"
last7days: "过去 7 天" last7days: "过去 7 天"
lastAllowed: "最后允许"
lastBlocked: "最后阻止"
lastConnected: "最后连接时间" lastConnected: "最后连接时间"
lastKnownAttempt: "最后一次已知的尝试"
lastPeak: "上次在线峰值" lastPeak: "上次在线峰值"
lastSeen: "最后在线时间" lastSeen: "最后在线时间"
latestJoinAddresses: "上一次加入地址" latestJoinAddresses: "上一次加入地址"
@ -655,7 +666,6 @@ html:
page_network_geolocations_ping_per_country: "查看按国家划分的Ping表" page_network_geolocations_ping_per_country: "查看按国家划分的Ping表"
page_network_join_addresses: "查看加入地址 - 选项卡" page_network_join_addresses: "查看加入地址 - 选项卡"
page_network_join_addresses_graphs: "查看加入地址图表" page_network_join_addresses_graphs: "查看加入地址图表"
page_network_join_addresses_graphs_pie: "查看最新加入地址图表"
page_network_join_addresses_graphs_time: "查看加入地址随时间变化的图表" page_network_join_addresses_graphs_time: "查看加入地址随时间变化的图表"
page_network_overview: "查看网络总览 - 选项卡" page_network_overview: "查看网络总览 - 选项卡"
page_network_overview_graphs: "查看网络总览图表" page_network_overview_graphs: "查看网络总览图表"
@ -685,12 +695,12 @@ html:
page_player_sessions: "查看玩家会话 - 选项卡" page_player_sessions: "查看玩家会话 - 选项卡"
page_player_versus: "查看PvP和PvE - 选项卡" page_player_versus: "查看PvP和PvE - 选项卡"
page_server: "查看所有服务器页面" page_server: "查看所有服务器页面"
page_server_allowlist_bounce: "查看游戏白名单退回列表"
page_server_geolocations: "查看服务器地理位置 - 选项卡" page_server_geolocations: "查看服务器地理位置 - 选项卡"
page_server_geolocations_map: "查看服务器地理位置地图" page_server_geolocations_map: "查看服务器地理位置地图"
page_server_geolocations_ping_per_country: "查看按国家划分的延迟表" page_server_geolocations_ping_per_country: "查看按国家划分的延迟表"
page_server_join_addresses: "查看服务器加入地址 - 选项卡" page_server_join_addresses: "查看服务器加入地址 - 选项卡"
page_server_join_addresses_graphs: "查看服务器加入地址图表" page_server_join_addresses_graphs: "查看服务器加入地址图表"
page_server_join_addresses_graphs_pie: "查看最新加入地址图表"
page_server_join_addresses_graphs_time: "查看服务器加入地址随时间变化的图表" page_server_join_addresses_graphs_time: "查看服务器加入地址随时间变化的图表"
page_server_online_activity: "查看在线活动 - 选项卡" page_server_online_activity: "查看在线活动 - 选项卡"
page_server_online_activity_graphs: "查看在线活动图表" page_server_online_activity_graphs: "查看在线活动图表"
@ -784,6 +794,7 @@ html:
generic: generic:
are: "`是`" are: "`是`"
label: label:
editQuery: "修改查询"
from: ">从 </label>" from: ">从 </label>"
makeAnother: "进行另一个查询" makeAnother: "进行另一个查询"
servers: servers:
@ -896,7 +907,7 @@ plugin:
no: "否" no: "否"
today: "'今天'" today: "'今天'"
unavailable: "不可用" unavailable: "不可用"
unknown: "位置" unknown: "未知"
yes: "是" yes: "是"
yesterday: "'昨天'" yesterday: "'昨天'"
localeReloaded: "自定义 locale.yml 已被修改,因此已重新加载并且现在正在使用。" localeReloaded: "自定义 locale.yml 已被修改,因此已重新加载并且现在正在使用。"

View File

@ -290,13 +290,19 @@ html:
active: "Aktivní" active: "Aktivní"
activePlaytime: "Aktivní herní čas" activePlaytime: "Aktivní herní čas"
activityIndex: "Index aktivity" activityIndex: "Index aktivity"
addJoinAddressGroup: "Add address group"
addressGroup: "Address group {{n}}"
afk: "AFK" afk: "AFK"
afkTime: "AFK čas" afkTime: "AFK čas"
all: "Vše" all: "Vše"
allTime: "Celkově" allTime: "Celkově"
allowed: "Allowed"
allowlist: "Allowlist"
allowlistBounces: "Allowlist Bounces"
alphabetical: "Abecední řazení" alphabetical: "Abecední řazení"
apply: "Apply" apply: "Apply"
asNumbers: "statistiky" asNumbers: "statistiky"
attempts: "Attempts"
average: "Průměrná délka prvního připojení" average: "Průměrná délka prvního připojení"
averageActivePlaytime: "Průměrná herní aktivita" averageActivePlaytime: "Průměrná herní aktivita"
averageAfkTime: "Průměrný AFK čas" averageAfkTime: "Průměrný AFK čas"
@ -317,6 +323,7 @@ html:
banned: "Zabanován" banned: "Zabanován"
bestPeak: "Nejvíce hráčů" bestPeak: "Nejvíce hráčů"
bestPing: "Nejlepší ping" bestPing: "Nejlepší ping"
blocked: "Blocked"
calendar: " Kalendář" calendar: " Kalendář"
comparing7days: "Srovnání posledních 7 dní" comparing7days: "Srovnání posledních 7 dní"
connectionInfo: "Informace o připojení" connectionInfo: "Informace o připojení"
@ -338,6 +345,7 @@ html:
duringLowTps: "Při nízkých TPS:" duringLowTps: "Při nízkých TPS:"
entities: "Entity" entities: "Entity"
errors: "Plan Error Logs" errors: "Plan Error Logs"
export: "Export"
exported: "Doba exportu dat" exported: "Doba exportu dat"
favoriteServer: "Oblíbený server" favoriteServer: "Oblíbený server"
firstSession: "První relace" firstSession: "První relace"
@ -429,7 +437,10 @@ html:
last24hours: "Posledních 24 hodin" last24hours: "Posledních 24 hodin"
last30days: "Posledních 30 dní" last30days: "Posledních 30 dní"
last7days: "Posledních 7 dní" last7days: "Posledních 7 dní"
lastAllowed: "Last Allowed"
lastBlocked: "Last Blocked"
lastConnected: "Poslední připojení" lastConnected: "Poslední připojení"
lastKnownAttempt: "Last Known Attempt"
lastPeak: "Naposledy nejvíce hráčů" lastPeak: "Naposledy nejvíce hráčů"
lastSeen: "Naposledy viděn" lastSeen: "Naposledy viděn"
latestJoinAddresses: "Poslední adresy pro připojení" latestJoinAddresses: "Poslední adresy pro připojení"
@ -655,7 +666,6 @@ html:
page_network_geolocations_ping_per_country: "See Ping Per Country table" page_network_geolocations_ping_per_country: "See Ping Per Country table"
page_network_join_addresses: "See Join Addresses -tab" page_network_join_addresses: "See Join Addresses -tab"
page_network_join_addresses_graphs: "See Join Address graphs" page_network_join_addresses_graphs: "See Join Address graphs"
page_network_join_addresses_graphs_pie: "See Latest Join Addresses graph"
page_network_join_addresses_graphs_time: "See Join Addresses over time graph" page_network_join_addresses_graphs_time: "See Join Addresses over time graph"
page_network_overview: "See Network Overview -tab" page_network_overview: "See Network Overview -tab"
page_network_overview_graphs: "See Network Overview graphs" page_network_overview_graphs: "See Network Overview graphs"
@ -685,12 +695,12 @@ html:
page_player_sessions: "See Player Sessions -tab" page_player_sessions: "See Player Sessions -tab"
page_player_versus: "See PvP & PvE -tab" page_player_versus: "See PvP & PvE -tab"
page_server: "See all of server page" page_server: "See all of server page"
page_server_allowlist_bounce: "See list of Game allowlist bounces"
page_server_geolocations: "See Geolocations tab" page_server_geolocations: "See Geolocations tab"
page_server_geolocations_map: "See Geolocations Map" page_server_geolocations_map: "See Geolocations Map"
page_server_geolocations_ping_per_country: "See Ping Per Country table" page_server_geolocations_ping_per_country: "See Ping Per Country table"
page_server_join_addresses: "See Join Addresses -tab" page_server_join_addresses: "See Join Addresses -tab"
page_server_join_addresses_graphs: "See Join Address graphs" page_server_join_addresses_graphs: "See Join Address graphs"
page_server_join_addresses_graphs_pie: "See Latest Join Addresses graph"
page_server_join_addresses_graphs_time: "See Join Addresses over time graph" page_server_join_addresses_graphs_time: "See Join Addresses over time graph"
page_server_online_activity: "See Online Activity -tab" page_server_online_activity: "See Online Activity -tab"
page_server_online_activity_graphs: "See Online Activity graphs" page_server_online_activity_graphs: "See Online Activity graphs"
@ -784,6 +794,7 @@ html:
generic: generic:
are: "` jsou`" are: "` jsou`"
label: label:
editQuery: "Edit Query"
from: ">od</label>" from: ">od</label>"
makeAnother: "Vytvořit další dotaz" makeAnother: "Vytvořit další dotaz"
servers: servers:

View File

@ -290,13 +290,19 @@ html:
active: "Aktiv" active: "Aktiv"
activePlaytime: "Aktive Spielzeit" activePlaytime: "Aktive Spielzeit"
activityIndex: "Aktivitätsindex" activityIndex: "Aktivitätsindex"
addJoinAddressGroup: "Add address group"
addressGroup: "Address group {{n}}"
afk: "AFK" afk: "AFK"
afkTime: "AFK Zeit" afkTime: "AFK Zeit"
all: "Gesamt" all: "Gesamt"
allTime: "Gesamte zeit" allTime: "Gesamte zeit"
allowed: "Allowed"
allowlist: "Allowlist"
allowlistBounces: "Allowlist Bounces"
alphabetical: "Alphabetical" alphabetical: "Alphabetical"
apply: "Apply" apply: "Apply"
asNumbers: "als Zahlen" asNumbers: "als Zahlen"
attempts: "Attempts"
average: "Average first session length" average: "Average first session length"
averageActivePlaytime: "Durchschnittliche aktive Spielzeit" averageActivePlaytime: "Durchschnittliche aktive Spielzeit"
averageAfkTime: "Durchschnittliche AFK Zeit" averageAfkTime: "Durchschnittliche AFK Zeit"
@ -317,6 +323,7 @@ html:
banned: "Gebannt" banned: "Gebannt"
bestPeak: "Rekord" bestPeak: "Rekord"
bestPing: "Bester Ping" bestPing: "Bester Ping"
blocked: "Blocked"
calendar: " Kalender" calendar: " Kalender"
comparing7days: "Vergleiche 7 Tage" comparing7days: "Vergleiche 7 Tage"
connectionInfo: "Verbindungsinformationen" connectionInfo: "Verbindungsinformationen"
@ -338,6 +345,7 @@ html:
duringLowTps: "Während niedriger TPS-Spitzen:" duringLowTps: "Während niedriger TPS-Spitzen:"
entities: "Entitäten" entities: "Entitäten"
errors: "Plan Error Logs" errors: "Plan Error Logs"
export: "Export"
exported: "Data export time" exported: "Data export time"
favoriteServer: "Lieblingsserver" favoriteServer: "Lieblingsserver"
firstSession: "Erste Sitzung" firstSession: "Erste Sitzung"
@ -429,7 +437,10 @@ html:
last24hours: "Letzte 24 Stunden" last24hours: "Letzte 24 Stunden"
last30days: "Letzte 30 Tage" last30days: "Letzte 30 Tage"
last7days: "Letzte 7 Tage" last7days: "Letzte 7 Tage"
lastAllowed: "Last Allowed"
lastBlocked: "Last Blocked"
lastConnected: "Letzte Verbindung" lastConnected: "Letzte Verbindung"
lastKnownAttempt: "Last Known Attempt"
lastPeak: "Letzter Höchststand" lastPeak: "Letzter Höchststand"
lastSeen: "Zuletzt gesehen" lastSeen: "Zuletzt gesehen"
latestJoinAddresses: "Latest Join Addresses" latestJoinAddresses: "Latest Join Addresses"
@ -655,7 +666,6 @@ html:
page_network_geolocations_ping_per_country: "See Ping Per Country table" page_network_geolocations_ping_per_country: "See Ping Per Country table"
page_network_join_addresses: "See Join Addresses -tab" page_network_join_addresses: "See Join Addresses -tab"
page_network_join_addresses_graphs: "See Join Address graphs" page_network_join_addresses_graphs: "See Join Address graphs"
page_network_join_addresses_graphs_pie: "See Latest Join Addresses graph"
page_network_join_addresses_graphs_time: "See Join Addresses over time graph" page_network_join_addresses_graphs_time: "See Join Addresses over time graph"
page_network_overview: "See Network Overview -tab" page_network_overview: "See Network Overview -tab"
page_network_overview_graphs: "See Network Overview graphs" page_network_overview_graphs: "See Network Overview graphs"
@ -685,12 +695,12 @@ html:
page_player_sessions: "See Player Sessions -tab" page_player_sessions: "See Player Sessions -tab"
page_player_versus: "See PvP & PvE -tab" page_player_versus: "See PvP & PvE -tab"
page_server: "See all of server page" page_server: "See all of server page"
page_server_allowlist_bounce: "See list of Game allowlist bounces"
page_server_geolocations: "See Geolocations tab" page_server_geolocations: "See Geolocations tab"
page_server_geolocations_map: "See Geolocations Map" page_server_geolocations_map: "See Geolocations Map"
page_server_geolocations_ping_per_country: "See Ping Per Country table" page_server_geolocations_ping_per_country: "See Ping Per Country table"
page_server_join_addresses: "See Join Addresses -tab" page_server_join_addresses: "See Join Addresses -tab"
page_server_join_addresses_graphs: "See Join Address graphs" page_server_join_addresses_graphs: "See Join Address graphs"
page_server_join_addresses_graphs_pie: "See Latest Join Addresses graph"
page_server_join_addresses_graphs_time: "See Join Addresses over time graph" page_server_join_addresses_graphs_time: "See Join Addresses over time graph"
page_server_online_activity: "See Online Activity -tab" page_server_online_activity: "See Online Activity -tab"
page_server_online_activity_graphs: "See Online Activity graphs" page_server_online_activity_graphs: "See Online Activity graphs"
@ -784,6 +794,7 @@ html:
generic: generic:
are: "`are`" are: "`are`"
label: label:
editQuery: "Edit Query"
from: ">from</label>" from: ">from</label>"
makeAnother: "Make another query" makeAnother: "Make another query"
servers: servers:

View File

@ -290,13 +290,19 @@ html:
active: "Active" active: "Active"
activePlaytime: "Active Playtime" activePlaytime: "Active Playtime"
activityIndex: "Activity Index" activityIndex: "Activity Index"
addJoinAddressGroup: "Add address group"
addressGroup: "Address group {{n}}"
afk: "AFK" afk: "AFK"
afkTime: "AFK Time" afkTime: "AFK Time"
all: "All" all: "All"
allTime: "All Time" allTime: "All Time"
allowed: "Allowed"
allowlist: "Allowlist"
allowlistBounces: "Allowlist Bounces"
alphabetical: "Alphabetical" alphabetical: "Alphabetical"
apply: "Apply" apply: "Apply"
asNumbers: "as Numbers" asNumbers: "as Numbers"
attempts: "Attempts"
average: "Average first session length" average: "Average first session length"
averageActivePlaytime: "Average Active Playtime" averageActivePlaytime: "Average Active Playtime"
averageAfkTime: "Average AFK Time" averageAfkTime: "Average AFK Time"
@ -317,6 +323,7 @@ html:
banned: "Banned" banned: "Banned"
bestPeak: "All Time Peak" bestPeak: "All Time Peak"
bestPing: "Best Ping" bestPing: "Best Ping"
blocked: "Blocked"
calendar: " Calendar" calendar: " Calendar"
comparing7days: "Comparing 7 days" comparing7days: "Comparing 7 days"
connectionInfo: "Connection Information" connectionInfo: "Connection Information"
@ -338,6 +345,7 @@ html:
duringLowTps: "During Low TPS Spikes:" duringLowTps: "During Low TPS Spikes:"
entities: "Entities" entities: "Entities"
errors: "Plan Error Logs" errors: "Plan Error Logs"
export: "Export"
exported: "Data export time" exported: "Data export time"
favoriteServer: "Favorite Server" favoriteServer: "Favorite Server"
firstSession: "First session" firstSession: "First session"
@ -429,7 +437,10 @@ html:
last24hours: "Last 24 hours" last24hours: "Last 24 hours"
last30days: "Last 30 days" last30days: "Last 30 days"
last7days: "Last 7 days" last7days: "Last 7 days"
lastAllowed: "Last Allowed"
lastBlocked: "Last Blocked"
lastConnected: "Last Connected" lastConnected: "Last Connected"
lastKnownAttempt: "Last Known Attempt"
lastPeak: "Last Peak" lastPeak: "Last Peak"
lastSeen: "Last Seen" lastSeen: "Last Seen"
latestJoinAddresses: "Latest Join Addresses" latestJoinAddresses: "Latest Join Addresses"
@ -655,7 +666,6 @@ html:
page_network_geolocations_ping_per_country: "See Ping Per Country table" page_network_geolocations_ping_per_country: "See Ping Per Country table"
page_network_join_addresses: "See Join Addresses -tab" page_network_join_addresses: "See Join Addresses -tab"
page_network_join_addresses_graphs: "See Join Address graphs" page_network_join_addresses_graphs: "See Join Address graphs"
page_network_join_addresses_graphs_pie: "See Latest Join Addresses graph"
page_network_join_addresses_graphs_time: "See Join Addresses over time graph" page_network_join_addresses_graphs_time: "See Join Addresses over time graph"
page_network_overview: "See Network Overview -tab" page_network_overview: "See Network Overview -tab"
page_network_overview_graphs: "See Network Overview graphs" page_network_overview_graphs: "See Network Overview graphs"
@ -685,12 +695,12 @@ html:
page_player_sessions: "See Player Sessions -tab" page_player_sessions: "See Player Sessions -tab"
page_player_versus: "See PvP & PvE -tab" page_player_versus: "See PvP & PvE -tab"
page_server: "See all of server page" page_server: "See all of server page"
page_server_allowlist_bounce: "See list of Game allowlist bounces"
page_server_geolocations: "See Geolocations tab" page_server_geolocations: "See Geolocations tab"
page_server_geolocations_map: "See Geolocations Map" page_server_geolocations_map: "See Geolocations Map"
page_server_geolocations_ping_per_country: "See Ping Per Country table" page_server_geolocations_ping_per_country: "See Ping Per Country table"
page_server_join_addresses: "See Join Addresses -tab" page_server_join_addresses: "See Join Addresses -tab"
page_server_join_addresses_graphs: "See Join Address graphs" page_server_join_addresses_graphs: "See Join Address graphs"
page_server_join_addresses_graphs_pie: "See Latest Join Addresses graph"
page_server_join_addresses_graphs_time: "See Join Addresses over time graph" page_server_join_addresses_graphs_time: "See Join Addresses over time graph"
page_server_online_activity: "See Online Activity -tab" page_server_online_activity: "See Online Activity -tab"
page_server_online_activity_graphs: "See Online Activity graphs" page_server_online_activity_graphs: "See Online Activity graphs"
@ -784,6 +794,7 @@ html:
generic: generic:
are: "`are`" are: "`are`"
label: label:
editQuery: "Edit Query"
from: ">from</label>" from: ">from</label>"
makeAnother: "Make another query" makeAnother: "Make another query"
servers: servers:

Some files were not shown because too many files have changed in this diff Show More