├── .github
├── dependabot.yml
└── workflows
│ ├── docs.yml
│ └── release.yml
├── .gitignore
├── LICENSE
├── README.md
├── create
├── .gitignore
├── index.html
├── jsconfig.json
├── package.json
├── public
│ ├── assets
│ │ └── img
│ │ │ ├── favicon.ico
│ │ │ └── favicon.png
│ ├── install.sh
│ ├── lookup.sh
│ └── plugin.sh
├── src
│ ├── App.jsx
│ ├── common
│ │ ├── contexts
│ │ │ └── SocketContext
│ │ │ │ ├── SocketContext.jsx
│ │ │ │ ├── index.js
│ │ │ │ └── socket.js
│ │ └── themes
│ │ │ └── default.js
│ ├── main.jsx
│ └── pages
│ │ ├── ExistingServer
│ │ ├── ExistingServer.jsx
│ │ ├── components
│ │ │ └── PluginInstaller
│ │ │ │ ├── PluginInstaller.jsx
│ │ │ │ └── index.js
│ │ └── index.js
│ │ └── NewServer
│ │ ├── NewServer.jsx
│ │ ├── components
│ │ ├── Account
│ │ │ ├── Account.jsx
│ │ │ └── index.js
│ │ ├── Finished
│ │ │ ├── Finished.jsx
│ │ │ ├── components
│ │ │ │ └── InstallationDialog
│ │ │ │ │ ├── InstallationDialog.jsx
│ │ │ │ │ └── index.js
│ │ │ └── index.js
│ │ ├── Game
│ │ │ ├── Game.jsx
│ │ │ └── index.js
│ │ └── Server
│ │ │ ├── Server.jsx
│ │ │ ├── index.js
│ │ │ └── versions.js
│ │ └── index.js
├── vite.config.js
└── yarn.lock
├── crowdin.yml
├── docs
├── .gitignore
├── babel.config.js
├── docs
│ ├── intro.md
│ ├── modules
│ │ ├── backups.md
│ │ ├── configuration.md
│ │ ├── console.md
│ │ ├── file_manager.md
│ │ ├── modules.md
│ │ ├── overview.md
│ │ ├── players.md
│ │ └── plugins.md
│ ├── plugins
│ │ └── plugins.md
│ └── wrapper
│ │ ├── create.md
│ │ ├── docker.md
│ │ └── wrapper.md
├── docusaurus.config.js
├── package.json
├── sidebars.js
├── src
│ ├── components
│ │ └── HomepageFeatures
│ │ │ ├── index.js
│ │ │ └── styles.module.css
│ ├── css
│ │ └── custom.css
│ └── pages
│ │ ├── index.js
│ │ └── index.module.css
├── static
│ ├── .nojekyll
│ ├── CNAME
│ └── img
│ │ ├── favicon.ico
│ │ ├── favicon.png
│ │ └── screenshots
│ │ ├── backups.png
│ │ ├── configuration.png
│ │ ├── console.png
│ │ ├── file_manager.png
│ │ ├── overview.png
│ │ ├── players.png
│ │ ├── plugins.png
│ │ └── wrapper
│ │ ├── create.png
│ │ ├── home.png
│ │ ├── menu.png
│ │ └── open.png
└── yarn.lock
├── pom.xml
├── src
└── main
│ ├── java
│ └── de
│ │ └── gnmyt
│ │ └── mcdash
│ │ ├── MinecraftDashboard.java
│ │ ├── api
│ │ ├── config
│ │ │ ├── AccountManager.java
│ │ │ ├── BackupManager.java
│ │ │ ├── ConfigurationManager.java
│ │ │ ├── Metrics.java
│ │ │ ├── SSHManager.java
│ │ │ ├── ScheduleManager.java
│ │ │ ├── UpdateManager.java
│ │ │ └── WorldManager.java
│ │ ├── controller
│ │ │ ├── BackupController.java
│ │ │ ├── SSHController.java
│ │ │ ├── ScheduleController.java
│ │ │ └── StatsController.java
│ │ ├── entities
│ │ │ ├── BackupMode.java
│ │ │ ├── Schedule.java
│ │ │ ├── ScheduleAction.java
│ │ │ ├── ScheduleActionType.java
│ │ │ ├── ScheduleExecution.java
│ │ │ └── ScheduleFrequency.java
│ │ ├── handler
│ │ │ ├── DefaultHandler.java
│ │ │ ├── MultipartHandler.java
│ │ │ └── StaticHandler.java
│ │ ├── http
│ │ │ ├── ContentType.java
│ │ │ ├── HTTPMethod.java
│ │ │ ├── HTTPRequestContext.java
│ │ │ ├── Request.java
│ │ │ ├── Response.java
│ │ │ └── ResponseController.java
│ │ ├── json
│ │ │ ├── ArrayBuilder.java
│ │ │ └── NodeBuilder.java
│ │ ├── ssh
│ │ │ ├── MCCommand.java
│ │ │ ├── MCShellFactory.java
│ │ │ └── SSHAuthenticator.java
│ │ └── tasks
│ │ │ └── TPSRunnable.java
│ │ ├── commands
│ │ └── PasswordCommand.java
│ │ └── panel
│ │ └── routes
│ │ ├── ConsoleRoute.java
│ │ ├── DefaultRoute.java
│ │ ├── PingRoute.java
│ │ ├── ServerInfoRoute.java
│ │ ├── UpdateRoute.java
│ │ ├── action
│ │ ├── ReloadRoute.java
│ │ ├── ShutdownRoute.java
│ │ └── WhitelistRoute.java
│ │ ├── backups
│ │ ├── BackupDownloadRoute.java
│ │ ├── BackupRestoreRoute.java
│ │ └── BackupRoute.java
│ │ ├── filebrowser
│ │ ├── FileRoute.java
│ │ └── FolderRoute.java
│ │ ├── manage
│ │ ├── PropertiesRoute.java
│ │ └── PropertyRoute.java
│ │ ├── players
│ │ ├── BanRoute.java
│ │ ├── KickRoute.java
│ │ ├── OnlineRoute.java
│ │ ├── OpRoute.java
│ │ ├── TeleportRoute.java
│ │ └── WhitelistRoute.java
│ │ ├── plugin
│ │ ├── PluginListRoute.java
│ │ └── PluginRoute.java
│ │ ├── schedules
│ │ ├── ScheduleActionRoute.java
│ │ ├── ScheduleExecutionRoute.java
│ │ ├── ScheduleNameRoute.java
│ │ └── ScheduleRoute.java
│ │ ├── services
│ │ └── SSHRoute.java
│ │ ├── stats
│ │ └── StatsRoute.java
│ │ ├── store
│ │ └── StoreRoute.java
│ │ └── worlds
│ │ ├── DifficultyRoute.java
│ │ ├── TimeRoute.java
│ │ ├── WeatherRoute.java
│ │ └── WorldsRoute.java
│ └── resources
│ └── plugin.yml
└── webui
├── .gitignore
├── index.html
├── jsconfig.json
├── package.json
├── public
└── assets
│ ├── img
│ ├── favicon.ico
│ └── favicon.png
│ └── locales
│ ├── da.json
│ ├── de.json
│ ├── en.json
│ ├── es.json
│ ├── fr.json
│ ├── ja.json
│ ├── nl.json
│ └── pl.json
├── src
├── App.jsx
├── common
│ ├── assets
│ │ └── images
│ │ │ ├── end.webp
│ │ │ ├── flags
│ │ │ ├── de.webp
│ │ │ ├── en.webp
│ │ │ ├── es.webp
│ │ │ ├── fr.webp
│ │ │ ├── ja.webp
│ │ │ └── pl.webp
│ │ │ ├── food.webp
│ │ │ ├── health.webp
│ │ │ ├── nether.webp
│ │ │ ├── overworld.webp
│ │ │ └── resource.webp
│ ├── components
│ │ └── ActionConfirmDialog
│ │ │ ├── ActionConfirmDialog.jsx
│ │ │ └── index.js
│ ├── contexts
│ │ ├── Players
│ │ │ ├── PlayerContext.jsx
│ │ │ └── index.js
│ │ ├── Settings
│ │ │ ├── SettingsContext.jsx
│ │ │ └── index.js
│ │ └── Token
│ │ │ ├── TokenContext.jsx
│ │ │ └── index.js
│ ├── routes
│ │ └── server.jsx
│ ├── themes
│ │ ├── dark.js
│ │ └── light.js
│ └── utils
│ │ ├── RequestUtil.js
│ │ └── StringUtil.js
├── i18n.js
├── main.jsx
└── states
│ ├── Login
│ ├── Login.jsx
│ └── index.js
│ └── Root
│ ├── Root.jsx
│ ├── components
│ ├── Header
│ │ ├── Header.jsx
│ │ ├── components
│ │ │ ├── AccountMenu
│ │ │ │ ├── AccountMenu.jsx
│ │ │ │ ├── components
│ │ │ │ │ └── ChangeLanguageDialog
│ │ │ │ │ │ ├── ChangeLanguageDialog.jsx
│ │ │ │ │ │ └── index.js
│ │ │ │ └── index.js
│ │ │ └── UpdateDialog
│ │ │ │ ├── UpdateDialog.jsx
│ │ │ │ └── index.js
│ │ └── index.js
│ └── Sidebar
│ │ ├── Sidebar.jsx
│ │ └── index.js
│ ├── index.js
│ └── pages
│ ├── Backups
│ ├── Backups.jsx
│ ├── components
│ │ ├── BackupCreationDialog
│ │ │ ├── BackupCreationDialog.jsx
│ │ │ └── index.js
│ │ └── BackupItem
│ │ │ ├── BackupItem.jsx
│ │ │ ├── components
│ │ │ └── RestoreDialog
│ │ │ │ ├── RestoreDialog.jsx
│ │ │ │ └── index.js
│ │ │ ├── index.js
│ │ │ └── mappings.jsx
│ ├── contexts
│ │ └── Backups
│ │ │ ├── BackupContext.jsx
│ │ │ └── index.js
│ └── index.js
│ ├── Configuration
│ ├── Configuration.jsx
│ ├── components
│ │ └── ConfigurationItem
│ │ │ ├── ConfigurationItem.jsx
│ │ │ └── index.js
│ ├── contexts
│ │ ├── Properties
│ │ │ ├── PropertiesContext.jsx
│ │ │ └── index.js
│ │ └── SSHStatus
│ │ │ ├── SSHStatusContext.jsx
│ │ │ └── index.js
│ └── index.js
│ ├── Console
│ ├── Console.jsx
│ ├── custom.css
│ └── index.js
│ ├── Files
│ ├── Files.jsx
│ ├── components
│ │ ├── FileDropdown
│ │ │ ├── FileDropdown.jsx
│ │ │ └── index.js
│ │ ├── FileEditor
│ │ │ ├── FileEditor.jsx
│ │ │ └── index.js
│ │ ├── FileHeader
│ │ │ ├── FileHeader.jsx
│ │ │ ├── components
│ │ │ │ └── NewFolderDialog
│ │ │ │ │ ├── NewFolderDialog.jsx
│ │ │ │ │ └── index.js
│ │ │ └── index.js
│ │ └── FileView
│ │ │ ├── FileView.jsx
│ │ │ ├── index.js
│ │ │ └── utils
│ │ │ └── FileUtil.js
│ └── index.js
│ ├── Overview
│ ├── Overview.jsx
│ ├── components
│ │ ├── ChartBox
│ │ │ ├── ChartBox.jsx
│ │ │ └── index.js
│ │ ├── OverviewArea
│ │ │ ├── OverviewArea.jsx
│ │ │ └── index.js
│ │ ├── StatisticBox
│ │ │ ├── StatisticBox.jsx
│ │ │ └── index.js
│ │ └── WelcomeTip
│ │ │ ├── WelcomeTip.jsx
│ │ │ └── index.js
│ ├── contexts
│ │ └── StatsContext
│ │ │ ├── StatsContext.jsx
│ │ │ └── index.js
│ └── index.js
│ ├── Players
│ ├── Players.jsx
│ ├── components
│ │ ├── BanListTable
│ │ │ ├── BanListTable.jsx
│ │ │ ├── columns.jsx
│ │ │ └── index.js
│ │ ├── PlayerActionDialog
│ │ │ ├── PlayerActionDialog.jsx
│ │ │ └── index.js
│ │ ├── PlayerTable
│ │ │ ├── PlayerTable.jsx
│ │ │ ├── columns.jsx
│ │ │ ├── components
│ │ │ │ └── PlayerTeleportDialog
│ │ │ │ │ ├── PlayerTeleportDialog.jsx
│ │ │ │ │ └── index.js
│ │ │ ├── index.js
│ │ │ └── utils
│ │ │ │ └── formatter.jsx
│ │ ├── WhiteListAddDialog
│ │ │ ├── WhitelistAddDialog.jsx
│ │ │ └── index.js
│ │ └── WhiteListTable
│ │ │ ├── WhiteListTable.jsx
│ │ │ ├── columns.jsx
│ │ │ └── index.js
│ ├── contexts
│ │ ├── BanList
│ │ │ ├── BanListContext.jsx
│ │ │ └── index.js
│ │ └── WhiteList
│ │ │ ├── WhiteListContext.jsx
│ │ │ └── index.js
│ └── index.js
│ ├── Plugins
│ ├── Plugins.jsx
│ ├── components
│ │ ├── PluginItem
│ │ │ ├── PluginItem.jsx
│ │ │ └── index.js
│ │ └── PluginStore
│ │ │ ├── PluginStore.jsx
│ │ │ ├── components
│ │ │ └── StoreItem
│ │ │ │ ├── StoreItem.jsx
│ │ │ │ ├── index.js
│ │ │ │ └── utils.js
│ │ │ └── index.js
│ ├── contexts
│ │ └── Plugins
│ │ │ ├── PluginsContext.jsx
│ │ │ └── index.js
│ └── index.js
│ ├── Scheduler
│ ├── Scheduler.jsx
│ ├── components
│ │ ├── Action
│ │ │ ├── Action.jsx
│ │ │ ├── components
│ │ │ │ └── EditActionDialog
│ │ │ │ │ ├── EditActionDialog.jsx
│ │ │ │ │ └── index.js
│ │ │ ├── index.js
│ │ │ └── mappings.jsx
│ │ ├── CreateActionDialog
│ │ │ ├── CreateActionDialog.jsx
│ │ │ └── index.js
│ │ ├── CreateScheduleDialog
│ │ │ ├── CreateScheduleDialog.jsx
│ │ │ ├── index.js
│ │ │ └── utils.js
│ │ └── Schedule
│ │ │ ├── Schedule.jsx
│ │ │ ├── index.js
│ │ │ └── utils.js
│ ├── contexts
│ │ └── Schedules
│ │ │ ├── SchedulesContext.jsx
│ │ │ └── index.js
│ └── index.js
│ ├── ServerDown
│ ├── ServerDown.jsx
│ └── index.js
│ └── Worlds
│ ├── Worlds.jsx
│ ├── components
│ ├── CreateWorldDialog
│ │ ├── CreateWorldDialog.jsx
│ │ └── index.js
│ └── WorldItem
│ │ ├── WorldItem.jsx
│ │ ├── components
│ │ ├── DifficultyDialog
│ │ │ ├── DifficultyDialog.jsx
│ │ │ └── index.js
│ │ ├── TimeDialog
│ │ │ ├── TimeDialog.jsx
│ │ │ └── index.js
│ │ ├── WeatherDialog
│ │ │ ├── WeatherDialog.jsx
│ │ │ └── index.js
│ │ ├── WorldHeader
│ │ │ ├── WorldHeader.jsx
│ │ │ └── index.js
│ │ └── WorldInfo
│ │ │ ├── WorldInfo.jsx
│ │ │ └── index.js
│ │ ├── index.js
│ │ └── utils.js
│ ├── contexts
│ └── Worlds
│ │ ├── WorldsContext.jsx
│ │ └── index.js
│ └── index.js
├── vite.config.js
└── yarn.lock
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "maven"
4 | directory: "/"
5 | schedule:
6 | interval: "daily"
--------------------------------------------------------------------------------
/.github/workflows/docs.yml:
--------------------------------------------------------------------------------
1 | name: Deploy to GitHub Pages
2 |
3 | on:
4 | push:
5 | branches: [ "master" ]
6 | paths: [ "docs/**" ]
7 |
8 | jobs:
9 | deploy:
10 | name: Deploy to GitHub Pages
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v3
14 | - uses: actions/setup-node@v3
15 | with:
16 | node-version: 18
17 |
18 | - name: Install dependencies
19 | working-directory: ./docs
20 | run: yarn install --frozen-lockfile
21 |
22 | - name: Build website
23 | working-directory: ./docs
24 | run: yarn build
25 |
26 | - name: Deploy to GitHub Pages
27 | uses: peaceiris/actions-gh-pages@v3
28 | with:
29 | github_token: ${{ secrets.GITHUB_TOKEN }}
30 | publish_dir: ./docs/build
31 | user_name: github-actions[bot]
32 | user_email: 41898282+github-actions[bot]@users.noreply.github.com
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Java CI
2 |
3 | on:
4 | push:
5 | tags: [ "v*" ]
6 |
7 | jobs:
8 | create_release:
9 | name: Create Release
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@v2
13 | - name: Set up JDK 8
14 | uses: actions/setup-java@v2
15 | with:
16 | java-version: '8'
17 | distribution: 'adopt'
18 |
19 | - name: Set up Node.js
20 | uses: actions/setup-node@v3
21 | with:
22 | node-version: 18
23 |
24 | - name: Build with Maven
25 | run: mvn clean compile assembly:single
26 |
27 | - name: Get version
28 | id: get_version
29 | run: echo "::set-output name=version::$(mvn -q -Dexec.executable=echo -Dexec.args='${project.version}' exec:exec)"
30 |
31 | - name: Get artifact id
32 | id: get_artifact_id
33 | run: echo "::set-output name=name::$(mvn -q -Dexec.executable=echo -Dexec.args='${project.artifactId}' exec:exec)"
34 |
35 | - name: Rename artifact
36 | run: mv ./target/${{ steps.get_artifact_id.outputs.name }}-${{ steps.get_version.outputs.version }}-jar-with-dependencies.jar ./target/${{ steps.get_artifact_id.outputs.name }}-${{ steps.get_version.outputs.version }}.jar
37 |
38 | - name: Automatic Releases
39 | uses: marvinpinto/action-automatic-releases@latest
40 | with:
41 | repo_token: ${{ secrets.GITHUB_TOKEN }}
42 | prerelease: false
43 | title: Release ${{ steps.get_version.outputs.version }}
44 | files: |
45 | ./target/${{ steps.get_artifact_id.outputs.name }}-${{ steps.get_version.outputs.version }}.jar
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | MCDash.iml
3 | /target/
4 | webui/dist
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Mathias Wagner
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/create/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/create/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | MCDash Creator
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/create/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "paths": {
5 | "@/*": ["./src/*"]
6 | }
7 | }
8 | }
--------------------------------------------------------------------------------
/create/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "create",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vite build",
9 | "lint": "eslint src --ext js,jsx --report-unused-disable-directives --max-warnings 0",
10 | "preview": "vite preview"
11 | },
12 | "dependencies": {
13 | "@emotion/react": "^11.11.1",
14 | "@emotion/styled": "^11.11.0",
15 | "@fontsource/inter": "^5.0.3",
16 | "@mui/icons-material": "^5.11.16",
17 | "@mui/material": "^5.13.6",
18 | "bcryptjs": "^2.4.3",
19 | "react": "^18.2.0",
20 | "react-dom": "^18.2.0",
21 | "socket.io-client": "^4.7.1"
22 | },
23 | "devDependencies": {
24 | "@types/react": "^18.0.37",
25 | "@types/react-dom": "^18.0.11",
26 | "@vitejs/plugin-react": "^4.0.0",
27 | "eslint": "^8.38.0",
28 | "eslint-plugin-react": "^7.32.2",
29 | "eslint-plugin-react-hooks": "^4.6.0",
30 | "eslint-plugin-react-refresh": "^0.3.4",
31 | "vite": "^4.3.9"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/create/public/assets/img/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gnmyt/MCDash/dddc7ebcd3961cac3bde8aebab45c00252edfc20/create/public/assets/img/favicon.ico
--------------------------------------------------------------------------------
/create/public/assets/img/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gnmyt/MCDash/dddc7ebcd3961cac3bde8aebab45c00252edfc20/create/public/assets/img/favicon.png
--------------------------------------------------------------------------------
/create/public/lookup.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | find_minecraft_servers() {
4 | local root_dir=$1
5 |
6 | while IFS= read -r -d '' file; do
7 | local server_path=$(dirname "$file")
8 | local plugins_dir="$server_path/plugins"
9 |
10 | if [[ -d "$plugins_dir" && ! -d "$plugins_dir/MinecraftDashboard" ]]; then
11 | echo "MCDash | [SERVER]$server_path[SERVER]"
12 | fi
13 | done < <(find "$root_dir" -name "bukkit.yml" -type f -print0)
14 | }
15 |
16 | find_minecraft_servers "/"
17 |
18 | echo "MCDash | [DONE]"
--------------------------------------------------------------------------------
/create/public/plugin.sh:
--------------------------------------------------------------------------------
1 | function install_plugin() {
2 | curl -L -o "$1/plugins/MCDash.jar" "https://api.spiget.org/v2/resources/110687/download"
3 | echo "Plugin installed. Restart your server to complete the installation."
4 | }
5 |
6 | if [[ -n $1 ]]; then
7 | install_plugin "$1"
8 | echo "MCDash | [IDONE]"
9 | exit
10 | fi
11 |
12 | echo "Available Minecraft servers:"
13 | IFS=$'\n'
14 |
15 | servers=($(find "/" -name bukkit.yml -exec dirname {} \; 2>/dev/null))
16 |
17 | select server in "${servers[@]}"; do
18 | if [[ -n $server ]]; then
19 | install_plugin "$server"
20 | break
21 | else
22 | echo "Invalid selection. Please try again."
23 | fi
24 | done
25 |
--------------------------------------------------------------------------------
/create/src/App.jsx:
--------------------------------------------------------------------------------
1 | import {Box, Link, Stack, Tab, Tabs, Typography} from "@mui/material";
2 | import {useState} from "react";
3 |
4 | import Icon from "/assets/img/favicon.png";
5 | import ExistingServer from "./pages/ExistingServer";
6 | import NewServer from "./pages/NewServer";
7 | import {SocketProvider} from "@/common/contexts/SocketContext";
8 |
9 | const App = () => {
10 | const [tab, setTab] = useState(0);
11 |
12 | return (
13 | <>
14 |
15 |
16 |
17 |
18 | MCDash Creator
19 |
20 |
21 |
23 |
24 | setTab(v)} sx={{width: 1, mb: 2}} variant="fullWidth">
25 |
26 |
27 |
28 |
29 |
30 | {tab === 0 && }
31 | {tab === 1 && }
32 |
33 |
34 |
35 |
36 | Imprint
38 | |
39 | Privacy
41 |
42 |
43 | >
44 | )
45 | }
46 |
47 | export default App;
--------------------------------------------------------------------------------
/create/src/common/contexts/SocketContext/SocketContext.jsx:
--------------------------------------------------------------------------------
1 | import {createContext, useEffect, useState} from "react";
2 | import {socket} from "./socket.js";
3 |
4 | export const SocketContext = createContext({});
5 |
6 | export const SocketProvider = ({children}) => {
7 |
8 | const [loginSuccess, setLoginSuccess] = useState(null);
9 | const [commands, setCommands] = useState([]);
10 |
11 | const connect = ({hostname, port = 22, username, password}) => {
12 | if (socket.connected) return;
13 |
14 | socket.connect();
15 |
16 | socket.on("type", () => {
17 | socket.emit("login", {port, host: hostname, username, password});
18 | });
19 | }
20 |
21 | const sendCommand = (command) => {
22 | if (!socket.connected) return;
23 |
24 | socket.emit("command", command + "\n");
25 | }
26 |
27 | const disconnect = () => {
28 | if (socket.connected) socket.disconnect();
29 | socket.off("type");
30 | setCommands([]);
31 | }
32 |
33 | useEffect(() => {
34 | const onConnect = () => socket.emit("type", "ssh");
35 |
36 | const onLogin = ({status}) => {
37 | if (status !== "success") {
38 | setLoginSuccess(false);
39 | return disconnect();
40 | }
41 | setLoginSuccess(true);
42 | }
43 |
44 | const onCommand = ({data}) => {
45 | if (!data) return;
46 | if (!data.toString().includes("MCDash | ")) return;
47 | if (data.toString().includes("\n")) return data.toString().split("\n").forEach(line => onCommand({data: line}));
48 |
49 | setCommands(prevCommands => [...prevCommands, data.toString().replace("MCDash | ", "")]);
50 | }
51 |
52 | socket.on("connect", onConnect);
53 | socket.on("login", onLogin);
54 | socket.on("command", onCommand);
55 |
56 | return () => {
57 | socket.off("connect", onConnect);
58 | socket.off("login", onLogin);
59 | socket.off("command", onCommand);
60 |
61 | if (socket.connected) disconnect();
62 | }
63 | }, []);
64 |
65 |
66 | return (
67 |
68 | {children}
69 |
70 | )
71 | }
--------------------------------------------------------------------------------
/create/src/common/contexts/SocketContext/index.js:
--------------------------------------------------------------------------------
1 | export * from "./SocketContext.jsx";
--------------------------------------------------------------------------------
/create/src/common/contexts/SocketContext/socket.js:
--------------------------------------------------------------------------------
1 | import { io } from "socket.io-client";
2 |
3 | export const socket = io("https://tools-api.gnmyt.dev", {autoConnect: false});
--------------------------------------------------------------------------------
/create/src/common/themes/default.js:
--------------------------------------------------------------------------------
1 | import { createTheme } from "@mui/material/styles";
2 |
3 | const theme = createTheme({
4 | palette: {
5 | mode: "dark",
6 | background: {
7 | default: "#27232F",
8 | darker: "#1A1722",
9 | },
10 | primary: {
11 | main: "#ce93d8",
12 | }
13 | },
14 | shape: {
15 | borderRadius: 10,
16 | },
17 | typography: {
18 | fontFamily: ["Inter", "sans-serif",].join(",")
19 | }
20 | });
21 |
22 | export default theme;
--------------------------------------------------------------------------------
/create/src/main.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom/client";
3 | import App from "./App.jsx";
4 | import theme from "./common/themes/default.js";
5 |
6 | import '@fontsource/inter/300.css';
7 | import '@fontsource/inter/400.css';
8 | import '@fontsource/inter/500.css';
9 | import '@fontsource/inter/700.css';
10 | import {CssBaseline, ThemeProvider} from "@mui/material";
11 |
12 | ReactDOM.createRoot(document.getElementById('root')).render(
13 |
14 |
15 |
16 |
17 |
18 | ,
19 | );
--------------------------------------------------------------------------------
/create/src/pages/ExistingServer/ExistingServer.jsx:
--------------------------------------------------------------------------------
1 | import {Alert, Button, IconButton, Link, Stack, TextField, Typography} from "@mui/material";
2 | import {Bolt, CopyAll} from "@mui/icons-material";
3 | import PluginInstaller from "@/pages/ExistingServer/components/PluginInstaller";
4 | import {useState} from "react";
5 |
6 | const command = "curl -sL -o /tmp/mcdash.sh https://create.mcdash.gnmyt.dev/plugin.sh && bash /tmp/mcdash.sh";
7 |
8 | export const ExistingServer = () => {
9 | const copy = () => navigator.clipboard.writeText(command);
10 |
11 | const [open, setOpen] = useState(false);
12 |
13 | const [installed, setInstalled] = useState(false);
14 |
15 | return (
16 | <>
17 | {installed && <>
18 | The plugin has been installed successfully!
19 |
20 |
21 | >}
22 | {!installed && open && }
23 | {!installed && !open &&
24 |
27 | }}/>
28 |
29 |
30 | OR
31 |
32 |
33 |
34 |
35 |
36 |
37 | This uses the
38 | PowerTools API
40 |
41 |
42 | }
43 | >
44 | )
45 | }
--------------------------------------------------------------------------------
/create/src/pages/ExistingServer/components/PluginInstaller/index.js:
--------------------------------------------------------------------------------
1 | export {PluginInstaller as default} from "./PluginInstaller.jsx";
--------------------------------------------------------------------------------
/create/src/pages/ExistingServer/index.js:
--------------------------------------------------------------------------------
1 | export {ExistingServer as default} from "./ExistingServer.jsx";
--------------------------------------------------------------------------------
/create/src/pages/NewServer/components/Account/Account.jsx:
--------------------------------------------------------------------------------
1 | import {Alert, IconButton, Stack, TextField} from "@mui/material";
2 | import {useState} from "react";
3 | import {Visibility, VisibilityOff} from "@mui/icons-material";
4 |
5 | export const Account = ({username, setUsername, password, setPassword}) => {
6 | const [passwordShown, setPasswordShown] = useState(false);
7 |
8 | return (
9 |
10 | Don't enter your Microsoft/Mojang account details here. Those credentials
11 | are only used to authenticate you with the panel.
12 | setUsername(e.target.value)} />
14 | setPassword(e.target.value)} type={passwordShown ? "text" : "password"}
16 | InputProps={{
17 | endAdornment: setPasswordShown(!passwordShown)}>{passwordShown
18 | ? : }
19 | }}
20 | />
21 |
22 | )
23 | }
24 |
--------------------------------------------------------------------------------
/create/src/pages/NewServer/components/Account/index.js:
--------------------------------------------------------------------------------
1 | export {Account as default} from "./Account.jsx";
--------------------------------------------------------------------------------
/create/src/pages/NewServer/components/Finished/components/InstallationDialog/index.js:
--------------------------------------------------------------------------------
1 | export {InstallationDialog as default} from "./InstallationDialog.jsx";
--------------------------------------------------------------------------------
/create/src/pages/NewServer/components/Finished/index.js:
--------------------------------------------------------------------------------
1 | export {Finished as default} from "./Finished.jsx";
--------------------------------------------------------------------------------
/create/src/pages/NewServer/components/Game/Game.jsx:
--------------------------------------------------------------------------------
1 | import {
2 | Checkbox,
3 | FormControlLabel,
4 | Link,
5 | Stack,
6 | TextField
7 | } from "@mui/material";
8 |
9 | const EULA_URL = "https://www.minecraft.net/eula";
10 |
11 | export const Game = ({eula, setEula, mcPort, setMcPort, panelPort, setPanelPort}) => {
12 | return (
13 | <>
14 |
15 |
16 | setMcPort(e.target.value)} type="number"/>
18 | setPanelPort(e.target.value)} type="number"/>
20 |
21 |
22 | setEula(e.target.checked)}/>}
23 | label={<>I accept the EULA>} />
24 |
25 | >
26 | )
27 | }
--------------------------------------------------------------------------------
/create/src/pages/NewServer/components/Game/index.js:
--------------------------------------------------------------------------------
1 | export {Game as default} from "./Game.jsx";
--------------------------------------------------------------------------------
/create/src/pages/NewServer/components/Server/Server.jsx:
--------------------------------------------------------------------------------
1 | import {Autocomplete, Box, FormControl, InputLabel, MenuItem, Select, Stack, TextField} from "@mui/material";
2 | import {getVersions} from "./versions.js";
3 |
4 | export const Server = ({software, setSoftware, version, setVersion, memory, setMemory, serverName, setServerName}) => {
5 |
6 | const updateSoftware = (software) => {
7 | if (!getVersions(software).includes(version)) setVersion(getVersions(software)[0]);
8 | setSoftware(software);
9 | }
10 |
11 | return (
12 | <>
13 |
14 |
15 |
16 | Software
17 |
23 |
24 |
25 | setVersion(v)}
27 | renderInput={(params) => }/>
28 |
29 |
30 |
31 | setServerName(e.target.value)}/>
33 | setMemory(e.target.value)}
35 | InputProps={{endAdornment: (GB)
37 | }}/>
38 |
39 |
40 | >);
41 | }
--------------------------------------------------------------------------------
/create/src/pages/NewServer/components/Server/index.js:
--------------------------------------------------------------------------------
1 | export {Server as default} from "./Server.jsx";
--------------------------------------------------------------------------------
/create/src/pages/NewServer/components/Server/versions.js:
--------------------------------------------------------------------------------
1 | const versions = [
2 | "1.20.1", "1.19.4", "1.19.3", "1.19.2", "1.19.1",
3 | "1.19", "1.18.2", "1.18.1", "1.18", "1.17.1", "1.17", "1.16.5",
4 | "1.16.4", "1.16.3", "1.16.2", "1.16.1", "1.15.2",
5 | "1.15.1", "1.15", "1.14.4", "1.14.3", "1.14.2", "1.14.1",
6 | "1.14", "1.13.2", "1.13.1", "1.13", "1.12.2", "1.12.1",
7 | "1.12", "1.11.2", "1.11.1", "1.11", "1.10.2", "1.10",
8 | "1.9.4", "1.9.2", "1.9", "1.8.8"
9 | ]
10 |
11 | export const getVersions = (server) => {
12 | if (server === "purpur") {
13 | return versions.filter(version => {
14 | if ( version === "1.14") return false;
15 | const versionNumber = version.split(".")[1];
16 | return versionNumber >= 14;
17 | });
18 | }
19 |
20 | if (server === "paper") {
21 | return versions.filter(version => {
22 | if (!["1.11.1", "1.11", "1.10", "1.9.2", "1.9"].includes(version)) return true;
23 | });
24 | }
25 |
26 | return versions;
27 | }
28 |
29 | export const getJavaVersion = (version) => {
30 | const versionNumber = version.split(".")[1];
31 | if (versionNumber >= 17) return 17;
32 | if (versionNumber >= 16) return 16;
33 | if (versionNumber >= 12) return 11;
34 | return 8;
35 | }
--------------------------------------------------------------------------------
/create/src/pages/NewServer/index.js:
--------------------------------------------------------------------------------
1 | export {NewServer as default} from "./NewServer.jsx";
--------------------------------------------------------------------------------
/create/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite";
2 | import react from "@vitejs/plugin-react";
3 | import path from "path";
4 |
5 | export default defineConfig({
6 | plugins: [react()],
7 | resolve: {
8 | alias: {
9 | "@": path.resolve(__dirname, "./src"),
10 | },
11 | },
12 | });
--------------------------------------------------------------------------------
/crowdin.yml:
--------------------------------------------------------------------------------
1 | files:
2 | - source: /webui/public/assets/locales/en.json
3 | translation: /webui/public/assets/locales/%two_letters_code%.json
4 |
--------------------------------------------------------------------------------
/docs/.gitignore:
--------------------------------------------------------------------------------
1 | # Dependencies
2 | /node_modules
3 |
4 | # Production
5 | /build
6 |
7 | # Generated files
8 | .docusaurus
9 | .cache-loader
10 |
11 | # Misc
12 | .DS_Store
13 | .env.local
14 | .env.development.local
15 | .env.test.local
16 | .env.production.local
17 |
18 | npm-debug.log*
19 | yarn-debug.log*
20 | yarn-error.log*
21 |
--------------------------------------------------------------------------------
/docs/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
3 | };
4 |
--------------------------------------------------------------------------------
/docs/docs/intro.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 1
3 | ---
4 |
5 | # Introduction
6 | MCDash is a simple dashboard for your Minecraft server. It allows you to see the current status of your server, the online players, console and more.
7 |
8 | # Installation
9 |
10 | [](https://www.spigotmc.org/resources/110687/)
11 | [](https://modrinth.com/plugin/mcdash)
12 | [](https://github.com/gnmyt/MCDash/releases/latest)
13 |
14 | 1. Choose one of the download links above and download the plugin
15 | 2. Upload the plugin into your `plugins` folder of your server
16 | 3. Restart your server
17 |
18 | # Creating your password
19 |
20 | To create your password, you have to execute the following command either in the console or in-game:
21 |
22 | ```
23 | /panel
24 | ```
25 |
26 | > **Note:** You have to replace `` with your password
27 |
28 | # Logging in
29 |
30 | To log in, you have to open the dashboard in your browser and enter your password. You can find the dashboard at `http://:7867/`.
31 | If you're running the server on your local machine, you can also use `http://localhost:7867/`.
--------------------------------------------------------------------------------
/docs/docs/modules/backups.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 1
3 | ---
4 |
5 | # Backups
6 |
7 | The backups module is responsible for the backups of the server. It allows you to create, restore, download and delete backups.
8 |
9 | 
10 |
11 | The icons next to the title show which files are included in the backup. Hover over the icons to see the file types.
12 | If the icon is a checkmark, the complete server directory is included in the backup.
13 |
14 | ## Create a backup
15 |
16 | To create a backup, click on the "Create" button. The backup will be created in the background and will be displayed in the list when it's done.
17 |
18 | ## Restore a backup
19 |
20 | To restore a backup, click on the "Restore" button of the backup you want to restore. The backup will be restored in the background.
--------------------------------------------------------------------------------
/docs/docs/modules/configuration.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 1
3 | ---
4 |
5 | # Configuration
6 |
7 | The configuration module is responsible for the configuration of the server. It allows you to edit the server.properties file quickly.
8 |
9 | 
10 |
11 | ## Configuration
12 |
13 | By default, the configuration module shows every entry from the server.properties file. You can edit the value of each entry by clicking on the value.
14 |
15 | Each configuration entry is either a boolean, a number or a string. The configuration module will automatically detect the type of each entry and will display the correct input field.
--------------------------------------------------------------------------------
/docs/docs/modules/console.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 1
3 | ---
4 |
5 | # Console
6 |
7 | The console module is responsible for the minecraft console. It allows you to send commands to the console and view the console output.
8 |
9 | 
10 |
11 | ## Sending Commands
12 |
13 | To send a command to the console, simply type the command into the input box and press enter. The command will be sent to the console and the output will be displayed in the console output box.
14 |
15 |
--------------------------------------------------------------------------------
/docs/docs/modules/file_manager.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 1
3 | ---
4 |
5 | # File Manager
6 |
7 | The file manager module is responsible for the management of the files of the server. It allows you to edit, delete and view the content of each file of the server.
8 |
9 | 
10 |
11 | ## Editing a file
12 |
13 | By default, the file manager module shows every file from the server. You can edit the content of each file by clicking on the file name.
14 | This will open a text editor where you can edit the content of the file.
15 |
16 | ## Deleting a file
17 |
18 | You can delete a file by clicking on the trash in the context menu of the file.
19 | Just use a right click on the file to open the context menu.
--------------------------------------------------------------------------------
/docs/docs/modules/modules.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 1
3 | ---
4 |
5 | # Modules
6 |
7 | MCDash is divided into `modules`. Each module is responsible for a specific part of the minecraft server. For example, the `Console` module is responsible for the minecraft console.
--------------------------------------------------------------------------------
/docs/docs/modules/overview.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 1
3 | ---
4 |
5 | # Overview
6 |
7 | The overview module provides a high-level overview of the current state of the system. It is the first module that is loaded when the user logs in.
8 |
9 | 
10 |
11 | ## Quick Overview
12 |
13 | The quick overview provides a quick overview of the current state of the system. It is divided into four sections:
14 |
15 | - **CPU-Cores**: Shows the current CPU-Cores assigned to the JVM and the total number of CPU-Cores available on the system.
16 | - **TPS**: Shows the current TPS (Ticks per second) of the system.
17 | - **Memory**: Shows the current memory assigned to the JVM and the total memory available on the system.
18 | - **Disk**: Shows the current disk space assigned to the JVM and the total disk space available on the system.
19 |
20 | ## Quick Control
21 |
22 | Here you can shut down and reload the server. When you click on the shutdown or reload button, a confirmation dialog will appear. If you confirm the dialog, the server will be shut down or reloaded.
--------------------------------------------------------------------------------
/docs/docs/modules/players.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 1
3 | ---
4 |
5 | # Players
6 |
7 | The players module is responsible for the management of the players on the server. It allows you to see all players on the server and to manage them.
8 |
9 | 
10 |
11 | ## Online Players
12 |
13 | The online players section shows all players that are currently online on the server. You can see the name, the UUID, the IP address and the current stats of each player.
14 |
15 | ## Whitelisted Players
16 |
17 | The whitelisted players section shows all players that are currently whitelisted on the server. You can see the name and the UUID of each player.
18 |
19 | ## Banned Players
20 |
21 | The banned players section shows all players that are currently banned on the server. You can see the name, the UUID, the reason address and the current stats of each player.
--------------------------------------------------------------------------------
/docs/docs/modules/plugins.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 1
3 | ---
4 |
5 | # Plugins
6 |
7 | The plugins module is responsible for the management of the plugins on the server. It allows you to see all plugins on the server and to manage them.
8 |
9 | 
10 |
11 | ## Plugins
12 |
13 | The plugins section shows all plugins that are currently installed on the server. You can see the name, the version, the description and the current stats of each plugin.
14 |
15 | ## Marketplace
16 |
17 | The marketplace section shows all plugins that are currently available on the marketplace. You can see the name, the version, the description and the current stats of each plugin.
--------------------------------------------------------------------------------
/docs/docs/plugins/plugins.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar_position: 1
3 | ---
4 |
5 | # Supported Plugins
6 |
7 | MCDash supports a wide range of plugins, and we're always adding more. If you want to see a plugin added, please open an issue on our [GitHub](https://github.com/gnmyt/MCDash/issues/new)
8 |
9 | ## Why?
10 |
11 | We want to make your experience managing a minecraft server as easy as possible. By hooking directly into your plugins, we can provide you with more information about your server, and allow you to manage it more efficiently.
--------------------------------------------------------------------------------
/docs/docs/wrapper/create.md:
--------------------------------------------------------------------------------
1 | # Create a new server
2 |
3 | Creating a new server is easy with MCDash. Simply follow the steps below to get started.
4 |
5 | ## Step 1: Click the "Create Server" button
6 |
7 | 
8 |
9 | You will be taken to this dialog where you can fill in the server details.
10 |
11 | ## Step 2: Fill in the server details
12 |
13 | You can choose a name and description for your server.
14 |
15 | ## Step 3: Choose a server type and version
16 |
17 | You can choose between Spigot, Paper, and Purpur. If you are unsure which one to choose, you can leave it at the default value.
18 | The wrapper will automatically manage the server files and Java versions for you.
19 |
20 | ## Step 4: Click "Create"
21 |
22 | Once you have filled in the details, click the "Create" button to create your server.
23 | This process might take a while to finish, depending on your server's performance.
24 |
25 | ## Finished
26 |
27 | You have now successfully created a new server. You can manage it from the dashboard.
--------------------------------------------------------------------------------
/docs/docs/wrapper/docker.md:
--------------------------------------------------------------------------------
1 | # Docker Installation
2 |
3 | If you plan on using Docker, you can install our pre-configured Docker image. This image contains all the necessary
4 | dependencies to run the code in this repository.
5 |
6 | ## Installation
7 |
8 | ### Automatic Port Exposure
9 |
10 | :::warning Important
11 | Using this method is not recommended for production environments. However, if you want a convenient way to run the
12 | code in this repository, you can use the following command to expose the necessary ports to the host machine.
13 | :::
14 |
15 | ```bash
16 | docker run -d -v mcdash:/app/data --restart=unless-stopped --network host --name MCDash germannewsmaker/mcdash
17 | ```
18 |
19 | ### Manual Port Exposure
20 |
21 | If you want to manually expose the necessary ports to the host machine, you can use the following command.
22 |
23 | :::tip Note
24 | The port 7865 is used by the MCDash web interface. You still need to add the necessary port mappings for the
25 | minecraft servers.
26 |
27 | :::
28 |
29 | ```bash
30 | docker run -d -v mcdash:/app/data --restart=unless-stopped -p 7865:7865 -p 25565:25565 --name MCDash germannewsmaker/mcdash
31 | ```
32 |
33 |
34 | ## Docker Compose
35 |
36 | If you want to use Docker Compose, you can use the following `docker-compose.yml` file.
37 |
38 | ### Automatic Port Exposure
39 |
40 | ```yaml
41 | version: '3.7'
42 | services:
43 | mcdash:
44 | image: germannewsmaker/mcdash
45 | container_name: MCDash
46 | restart: unless-stopped
47 | volumes:
48 | - mcdash:/app/data
49 | network_mode: host
50 | volumes:
51 | mcdash:
52 | ```
53 |
54 | ### Without Port Exposure
55 |
56 | ```yaml
57 | version: '3.7'
58 | services:
59 | mcdash:
60 | image: germannewsmaker/mcdash
61 | container_name: MCDash
62 | restart: unless-stopped
63 | volumes:
64 | - mcdash:/app/data
65 | ports:
66 | - 7865:7865
67 | - 25565:25565 # Add the necessary port mappings for the minecraft servers
68 | volumes:
69 | mcdash:
70 | ```
--------------------------------------------------------------------------------
/docs/docs/wrapper/wrapper.md:
--------------------------------------------------------------------------------
1 | # Wrapper
2 |
3 | MCDash Wrapper wraps the existing MCDash plugin and adds additional features to it.
4 |
5 | You are now able to create servers within 30 seconds, and manage them with ease.
6 |
7 | ## Inspiration
8 |
9 | The inspiration for this project came from Pterodactyl's Daemon and Panel. I wanted to create a similar system for
10 | Minecraft servers, but with a focus on simplicity and ease of use.
11 |
12 | ## Features
13 |
14 | - Automatically manages Java versions and server files
15 | - Support for Spigot, Paper, and Purpur
16 | - Easy to use web interface
17 |
18 | ## Installation
19 |
20 | If you are running MCDash on a Linux server, you can use the following script to install the wrapper:
21 |
22 | ```bash
23 | curl -sSL https://install-mcdash.gnmyt.dev/ | bash
24 | ```
25 |
26 | If you are running MCDash on a Windows server, you can download the latest version from GitHub and run it.
27 |
28 | ## Setup
29 |
30 | If the installation was successful, you can now access the wrapper by visiting the following URL:
31 | `http://:7865/`.
32 | If you are running the wrapper on your local machine, you can access it by visiting `http://localhost:7865/`.
33 |
34 | You will be prompted to create an account and log in. Once you have done that, you can start creating servers.
35 |
36 | ## Screenshots
37 |
38 | 
39 |
40 | 
41 |
42 | 
--------------------------------------------------------------------------------
/docs/docusaurus.config.js:
--------------------------------------------------------------------------------
1 | const lightCodeTheme = require('prism-react-renderer/themes/github');
2 | const darkCodeTheme = require('prism-react-renderer/themes/dracula');
3 |
4 | /** @type {import('@docusaurus/types').Config} */
5 | const config = {
6 | title: 'MCDash',
7 | tagline: 'MCDash is a simple dashboard for your Minecraft server.',
8 | favicon: 'img/favicon.ico',
9 | url: 'https://mcdash.gnmyt.dev',
10 | baseUrl: '/',
11 | organizationName: 'gnmyt',
12 | projectName: 'MCDash',
13 | onBrokenLinks: 'throw',
14 | onBrokenMarkdownLinks: 'warn',
15 | i18n: {defaultLocale: 'en', locales: ['en']},
16 | presets: [
17 | ['classic', ({
18 | docs: {sidebarPath: require.resolve('./sidebars.js'),
19 | editUrl: 'https://github.com/gnmyt/MCDash/tree/master/docs/'},
20 | theme: {customCss: require.resolve('./src/css/custom.css')},
21 | }),
22 | ],
23 | ],
24 |
25 | themeConfig:
26 | ({
27 | navbar: {
28 | title: 'MCDash',
29 | logo: {alt: 'MCDash', src: 'img/favicon.png'},
30 | items: [
31 | {type: 'docSidebar', sidebarId: 'default', position: 'left', label: 'Documentation'},
32 | {href: 'https://github.com/gnmyt/MCDash', label: 'GitHub', position: 'right'},
33 | ],
34 | },
35 | footer: {
36 | style: 'dark',
37 | copyright: `Copyright © ${new Date().getFullYear()} MCDash. Built with Docusaurus.`,
38 | },
39 | prism: {theme: lightCodeTheme, darkTheme: darkCodeTheme},
40 | }),
41 | };
42 |
43 | module.exports = config;
44 |
--------------------------------------------------------------------------------
/docs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "docs",
3 | "version": "0.0.0",
4 | "private": true,
5 | "scripts": {
6 | "docusaurus": "docusaurus",
7 | "start": "docusaurus start",
8 | "build": "docusaurus build",
9 | "swizzle": "docusaurus swizzle",
10 | "deploy": "docusaurus deploy",
11 | "clear": "docusaurus clear",
12 | "serve": "docusaurus serve",
13 | "write-translations": "docusaurus write-translations",
14 | "write-heading-ids": "docusaurus write-heading-ids"
15 | },
16 | "dependencies": {
17 | "@docusaurus/core": "2.4.1",
18 | "@docusaurus/preset-classic": "2.4.1",
19 | "@mdx-js/react": "^1.6.22",
20 | "clsx": "^1.2.1",
21 | "prism-react-renderer": "^1.3.5",
22 | "react": "^17.0.2",
23 | "react-dom": "^17.0.2"
24 | },
25 | "devDependencies": {
26 | "@docusaurus/module-type-aliases": "2.4.1"
27 | },
28 | "browserslist": {
29 | "production": [
30 | ">0.5%",
31 | "not dead",
32 | "not op_mini all"
33 | ],
34 | "development": [
35 | "last 1 chrome version",
36 | "last 1 firefox version",
37 | "last 1 safari version"
38 | ]
39 | },
40 | "engines": {
41 | "node": ">=16.14"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/docs/sidebars.js:
--------------------------------------------------------------------------------
1 | // @ts-check
2 |
3 | /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */
4 | const sidebars = {
5 | default: [{type: 'autogenerated', dirName: '.'}],
6 | };
7 |
8 | module.exports = sidebars;
9 |
--------------------------------------------------------------------------------
/docs/src/components/HomepageFeatures/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import clsx from 'clsx';
3 | import styles from './styles.module.css';
4 |
5 | const FeatureList = [
6 | {
7 | title: 'Overview',
8 | Image: require('@site/static/img/screenshots/overview.png').default,
9 | description: (
10 | <>
11 | Get a quick overview of what your server statistics are. You can also reload and
12 | shutdown your server with just one button click.
13 | >
14 | ),
15 | },
16 | {
17 | title: 'Manage players',
18 | Image: require('@site/static/img/screenshots/players.png').default,
19 | description: (
20 | <>
21 | Manage your currently online players. Get a quick glance of what they are
22 | up to and kick/ban them from your server.
23 | >
24 | ),
25 | },
26 | {
27 | title: 'Manage the console',
28 | Image: require('@site/static/img/screenshots/console.png').default,
29 | description: (
30 | <>
31 | You don't have to SSH into your server every time you just want to OP
32 | someone. Use a cloud console to do everything.
33 | >
34 | ),
35 | }
36 | ];
37 |
38 | function Feature({Image, title, description}) {
39 | return (
40 |
41 |
42 |

43 |
44 |
45 |
{title}
46 |
{description}
47 |
48 |
49 | );
50 | }
51 |
52 | export default function HomepageFeatures() {
53 | return (
54 |
55 |
56 |
57 | {FeatureList.map((props, idx) => (
58 |
59 | ))}
60 |
61 |
62 |
63 | );
64 | }
65 |
--------------------------------------------------------------------------------
/docs/src/components/HomepageFeatures/styles.module.css:
--------------------------------------------------------------------------------
1 | .features {
2 | display: flex;
3 | align-items: center;
4 | padding: 2rem 0;
5 | width: 100%;
6 | }
--------------------------------------------------------------------------------
/docs/src/css/custom.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --ifm-color-primary: #4cd4e8;
3 | --ifm-color-primary-dark: #3ea9bc;
4 | --ifm-color-primary-darker: #399aab;
5 | --ifm-color-primary-darkest: #256e7b;
6 | --ifm-color-primary-light: #4ee9ff;
7 | --ifm-color-primary-lighter: #2be7ff;
8 | --ifm-color-primary-lightest: #00dfff;
9 | --ifm-code-font-size: 95%;
10 | --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1);
11 | }
12 |
13 | [data-theme='dark'] {
14 | --ifm-color-primary: #4cd4e8;
15 | --ifm-color-primary-dark: #3ea9bc;
16 | --ifm-color-primary-darker: #399aab;
17 | --ifm-color-primary-darkest: #256e7b;
18 | --ifm-color-primary-light: #4ee9ff;
19 | --ifm-color-primary-lighter: #2be7ff;
20 | --ifm-color-primary-lightest: #00dfff;
21 | --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3);
22 | }
23 |
--------------------------------------------------------------------------------
/docs/src/pages/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import clsx from 'clsx';
3 | import Link from '@docusaurus/Link';
4 | import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
5 | import Layout from '@theme/Layout';
6 | import HomepageFeatures from '@site/src/components/HomepageFeatures';
7 |
8 | import styles from './index.module.css';
9 |
10 | function HomepageHeader() {
11 | const {siteConfig} = useDocusaurusContext();
12 | return (
13 |
27 | );
28 | }
29 |
30 | export default function Home() {
31 | const {siteConfig} = useDocusaurusContext();
32 | return (
33 |
34 |
35 |
36 |
37 |
38 |
39 | );
40 | }
41 |
--------------------------------------------------------------------------------
/docs/src/pages/index.module.css:
--------------------------------------------------------------------------------
1 | .heroBanner {
2 | padding: 4rem 0;
3 | text-align: center;
4 | position: relative;
5 | overflow: hidden;
6 | }
7 |
8 | @media screen and (max-width: 996px) {
9 | .heroBanner {
10 | padding: 2rem;
11 | }
12 | }
13 |
14 | .buttons {
15 | display: flex;
16 | align-items: center;
17 | justify-content: center;
18 | gap: 1rem;
19 | }
20 |
21 | @media screen and (max-width: 996px) {
22 | .buttons {
23 | flex-direction: column;
24 | }
25 | }
--------------------------------------------------------------------------------
/docs/static/.nojekyll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gnmyt/MCDash/dddc7ebcd3961cac3bde8aebab45c00252edfc20/docs/static/.nojekyll
--------------------------------------------------------------------------------
/docs/static/CNAME:
--------------------------------------------------------------------------------
1 | mcdash.gnm.dev
2 |
--------------------------------------------------------------------------------
/docs/static/img/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gnmyt/MCDash/dddc7ebcd3961cac3bde8aebab45c00252edfc20/docs/static/img/favicon.ico
--------------------------------------------------------------------------------
/docs/static/img/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gnmyt/MCDash/dddc7ebcd3961cac3bde8aebab45c00252edfc20/docs/static/img/favicon.png
--------------------------------------------------------------------------------
/docs/static/img/screenshots/backups.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gnmyt/MCDash/dddc7ebcd3961cac3bde8aebab45c00252edfc20/docs/static/img/screenshots/backups.png
--------------------------------------------------------------------------------
/docs/static/img/screenshots/configuration.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gnmyt/MCDash/dddc7ebcd3961cac3bde8aebab45c00252edfc20/docs/static/img/screenshots/configuration.png
--------------------------------------------------------------------------------
/docs/static/img/screenshots/console.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gnmyt/MCDash/dddc7ebcd3961cac3bde8aebab45c00252edfc20/docs/static/img/screenshots/console.png
--------------------------------------------------------------------------------
/docs/static/img/screenshots/file_manager.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gnmyt/MCDash/dddc7ebcd3961cac3bde8aebab45c00252edfc20/docs/static/img/screenshots/file_manager.png
--------------------------------------------------------------------------------
/docs/static/img/screenshots/overview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gnmyt/MCDash/dddc7ebcd3961cac3bde8aebab45c00252edfc20/docs/static/img/screenshots/overview.png
--------------------------------------------------------------------------------
/docs/static/img/screenshots/players.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gnmyt/MCDash/dddc7ebcd3961cac3bde8aebab45c00252edfc20/docs/static/img/screenshots/players.png
--------------------------------------------------------------------------------
/docs/static/img/screenshots/plugins.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gnmyt/MCDash/dddc7ebcd3961cac3bde8aebab45c00252edfc20/docs/static/img/screenshots/plugins.png
--------------------------------------------------------------------------------
/docs/static/img/screenshots/wrapper/create.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gnmyt/MCDash/dddc7ebcd3961cac3bde8aebab45c00252edfc20/docs/static/img/screenshots/wrapper/create.png
--------------------------------------------------------------------------------
/docs/static/img/screenshots/wrapper/home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gnmyt/MCDash/dddc7ebcd3961cac3bde8aebab45c00252edfc20/docs/static/img/screenshots/wrapper/home.png
--------------------------------------------------------------------------------
/docs/static/img/screenshots/wrapper/menu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gnmyt/MCDash/dddc7ebcd3961cac3bde8aebab45c00252edfc20/docs/static/img/screenshots/wrapper/menu.png
--------------------------------------------------------------------------------
/docs/static/img/screenshots/wrapper/open.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gnmyt/MCDash/dddc7ebcd3961cac3bde8aebab45c00252edfc20/docs/static/img/screenshots/wrapper/open.png
--------------------------------------------------------------------------------
/src/main/java/de/gnmyt/mcdash/api/config/AccountManager.java:
--------------------------------------------------------------------------------
1 | package de.gnmyt.mcdash.api.config;
2 |
3 | import de.gnmyt.mcdash.MinecraftDashboard;
4 | import org.bukkit.configuration.file.FileConfiguration;
5 | import org.bukkit.configuration.file.YamlConfiguration;
6 | import org.mindrot.jbcrypt.BCrypt;
7 |
8 | import java.io.File;
9 | import java.io.IOException;
10 | import java.util.ArrayList;
11 |
12 | public class AccountManager {
13 |
14 | private final File file;
15 | private final FileConfiguration config;
16 |
17 | /**
18 | * Basic constructor of the {@link AccountManager}
19 | * Loads the accounts.yml file
20 | */
21 | public AccountManager(MinecraftDashboard api) {
22 | file = new File("plugins//"+api.getName()+"//accounts.yml");
23 |
24 | config = YamlConfiguration.loadConfiguration(file);
25 |
26 | if (!config.contains("accounts")) {
27 | config.set("accounts", new ArrayList<>());
28 | saveConfig();
29 | }
30 | }
31 |
32 | /**
33 | * Adds an account to the accounts.yml file
34 | * @param username The username of the account
35 | * @param password The password of the account
36 | */
37 | public void register(String username, String password) {
38 | config.set("accounts." + username, BCrypt.hashpw(password, BCrypt.gensalt()));
39 | saveConfig();
40 | }
41 |
42 | /**
43 | * Checks if an account exists
44 | * @param username The username of the account
45 | * @return true
if the account exists, false
otherwise
46 | */
47 | public boolean accountExists(String username) {
48 | return config.contains("accounts." + username);
49 | }
50 |
51 | /**
52 | * Checks if the provided password is valid
53 | * @param username The username of the account
54 | * @param password The password of the account
55 | * @return true
if the password is valid, false
otherwise
56 | */
57 | public boolean isValidPassword(String username, String password) {
58 | if (!accountExists(username)) return false;
59 |
60 | return BCrypt.checkpw(password, config.getString("accounts." + username));
61 | }
62 |
63 | /**
64 | * Saves the configuration
65 | */
66 | private void saveConfig() {
67 | try {
68 | config.save(file);
69 | } catch (IOException e) {
70 | e.printStackTrace();
71 | }
72 | }
73 |
74 | }
75 |
--------------------------------------------------------------------------------
/src/main/java/de/gnmyt/mcdash/api/config/BackupManager.java:
--------------------------------------------------------------------------------
1 | package de.gnmyt.mcdash.api.config;
2 |
3 | import de.gnmyt.mcdash.MinecraftDashboard;
4 | import org.bukkit.configuration.file.FileConfiguration;
5 | import org.bukkit.configuration.file.YamlConfiguration;
6 |
7 | import java.io.File;
8 | import java.io.IOException;
9 |
10 | public class BackupManager {
11 |
12 | private final File file;
13 | private final FileConfiguration config;
14 |
15 | /**
16 | * Basic constructor of the {@link BackupManager}
17 | * Loads the backups.yml file
18 | */
19 | public BackupManager(MinecraftDashboard api) {
20 | file = new File("plugins//"+api.getName()+"//backups.yml");
21 |
22 | config = YamlConfiguration.loadConfiguration(file);
23 |
24 | if (!config.contains("path")) config.set("path", "backups");
25 |
26 | saveConfig();
27 | }
28 |
29 | /**
30 | * Gets the backup path
31 | *
32 | * @return the backup path
33 | */
34 | public String getBackupPath() { return config.getString("path"); }
35 |
36 |
37 | /**
38 | * Saves the configuration
39 | */
40 | private void saveConfig() {
41 | try {
42 | config.save(file);
43 | } catch (IOException e) {
44 | e.printStackTrace();
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/java/de/gnmyt/mcdash/api/config/ConfigurationManager.java:
--------------------------------------------------------------------------------
1 | package de.gnmyt.mcdash.api.config;
2 |
3 | import de.gnmyt.mcdash.MinecraftDashboard;
4 | import org.apache.commons.lang.RandomStringUtils;
5 | import org.bukkit.configuration.file.FileConfiguration;
6 |
7 | import java.io.File;
8 |
9 | public class ConfigurationManager {
10 |
11 | private final MinecraftDashboard api;
12 | private final FileConfiguration config;
13 |
14 | /**
15 | * Basic constructor of the {@link ConfigurationManager}
16 | * @param api The main instance of the plugin
17 | */
18 | public ConfigurationManager(MinecraftDashboard api) {
19 | this.api = api;
20 | config = api.getConfig();
21 | }
22 |
23 | /**
24 | * Checks if the config exists
25 | * @return true when the configuration file exists
26 | */
27 | public boolean configExists() {
28 | return new File("plugins//"+api.getName()+"//config.yml").exists();
29 | }
30 |
31 | /**
32 | * Generates a default configuration
33 | */
34 | public void generateDefault() {
35 | // Wrapper configuration
36 | config.set("port", 7867);
37 |
38 | saveConfig();
39 | }
40 |
41 | /**
42 | * Gets an string from the configuration
43 | * @param path The path you want to get
44 | * @return the value of the string
45 | */
46 | public String getString(String path) {
47 | return config.getString(path);
48 | }
49 |
50 | /**
51 | * Gets an integer from the configuration
52 | * @param path The path you want to get
53 | * @return the value of the integer
54 | */
55 | public Integer getInt(String path) {
56 | return config.getInt(path);
57 | }
58 |
59 | /**
60 | * Checks if the configuration file contains a string
61 | * @param path The path you want to check
62 | * @return true
if the provided path exists in the config, otherwise false
63 | */
64 | public boolean hasString(String path) {
65 | return config.getString(path) != null;
66 | }
67 |
68 | /**
69 | * Gets the port from the configuration
70 | * @return the port
71 | */
72 | public int getPort() {
73 | return getInt("port");
74 | }
75 |
76 |
77 | /**
78 | * Saves the current configuration
79 | */
80 | public void saveConfig() {
81 | api.saveConfig();
82 | }
83 |
84 | }
85 |
--------------------------------------------------------------------------------
/src/main/java/de/gnmyt/mcdash/api/config/WorldManager.java:
--------------------------------------------------------------------------------
1 | package de.gnmyt.mcdash.api.config;
2 |
3 | import de.gnmyt.mcdash.MinecraftDashboard;
4 | import org.bukkit.Bukkit;
5 | import org.bukkit.WorldCreator;
6 | import org.bukkit.configuration.file.FileConfiguration;
7 | import org.bukkit.configuration.file.YamlConfiguration;
8 |
9 | import java.io.File;
10 | import java.io.IOException;
11 | import java.util.ArrayList;
12 |
13 | public class WorldManager {
14 |
15 | private final File file;
16 | private final FileConfiguration config;
17 |
18 | /**
19 | * Basic constructor of the {@link WorldManager}
20 | * Loads the worlds.yml file
21 | */
22 | public WorldManager(MinecraftDashboard api) {
23 | file = new File("plugins//"+api.getName()+"//worlds.yml");
24 |
25 | config = YamlConfiguration.loadConfiguration(file);
26 |
27 | if (!config.contains("worlds")) {
28 | config.set("worlds", new ArrayList<>());
29 | saveConfig();
30 | }
31 |
32 | loadExistingWorlds();
33 | }
34 |
35 | /**
36 | * Loads all existing worlds
37 | */
38 | public void loadExistingWorlds() {
39 | for (String world : getWorlds()) {
40 | if (new File(world).exists()) {
41 | Bukkit.createWorld(new WorldCreator(world));
42 | } else {
43 | removeWorld(world);
44 | }
45 | }
46 | }
47 |
48 | /**
49 | * Gets all worlds
50 | * @return the worlds
51 | */
52 | public ArrayList getWorlds() {
53 | return (ArrayList) config.getStringList("worlds");
54 | }
55 |
56 | /**
57 | * Adds a new world
58 | * @param world The world you want to add
59 | */
60 | public void addWorld(String world) {
61 | ArrayList worlds = getWorlds();
62 | worlds.add(world);
63 | config.set("worlds", worlds);
64 | saveConfig();
65 | }
66 |
67 | /**
68 | * Removes a world
69 | * @param world The world you want to remove
70 | */
71 | public void removeWorld(String world) {
72 | ArrayList worlds = getWorlds();
73 | worlds.remove(world);
74 | config.set("worlds", worlds);
75 | saveConfig();
76 | }
77 |
78 |
79 | /**
80 | * Saves the configuration
81 | */
82 | private void saveConfig() {
83 | try {
84 | config.save(file);
85 | } catch (IOException e) {
86 | e.printStackTrace();
87 | }
88 | }
89 |
90 | }
91 |
--------------------------------------------------------------------------------
/src/main/java/de/gnmyt/mcdash/api/controller/SSHController.java:
--------------------------------------------------------------------------------
1 | package de.gnmyt.mcdash.api.controller;
2 |
3 | import de.gnmyt.mcdash.MinecraftDashboard;
4 | import de.gnmyt.mcdash.api.ssh.MCShellFactory;
5 | import de.gnmyt.mcdash.api.ssh.SSHAuthenticator;
6 | import org.apache.sshd.common.file.virtualfs.VirtualFileSystemFactory;
7 | import org.apache.sshd.server.SshServer;
8 | import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
9 | import org.apache.sshd.sftp.server.SftpSubsystemFactory;
10 |
11 | import java.io.IOException;
12 | import java.nio.file.Path;
13 | import java.nio.file.Paths;
14 | import java.util.Collections;
15 |
16 | public class SSHController {
17 |
18 | private SshServer sshServer;
19 | private final Path hostKey;
20 |
21 | /**
22 | * Basic constructor of the {@link SSHController}
23 | * @param api The current instance of the {@link MinecraftDashboard} api
24 | */
25 | public SSHController(MinecraftDashboard api) {
26 | hostKey = Paths.get("plugins//" + api.getName() + "//hostkey.ser");
27 | }
28 |
29 | /**
30 | * Starts the SSH server
31 | * @param port The port of the SSH server
32 | * @throws IOException Will be thrown if the SSH server could not be started
33 | */
34 | public void start(int port) throws IOException {
35 | if (sshServer != null) sshServer.stop();
36 |
37 | sshServer = SshServer.setUpDefaultServer();
38 | sshServer.setShellFactory(new MCShellFactory());
39 | sshServer.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(hostKey));
40 |
41 | sshServer.setFileSystemFactory(new VirtualFileSystemFactory(Paths.get(".").toAbsolutePath().normalize()));
42 | sshServer.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory.Builder().build()));
43 |
44 | sshServer.setPasswordAuthenticator(new SSHAuthenticator());
45 |
46 | sshServer.setPort(port);
47 | sshServer.start();
48 | }
49 |
50 | /**
51 | * Stops the SSH server
52 | * @throws IOException Will be thrown if the SSH server could not be stopped
53 | */
54 | public void stop() throws IOException {
55 | if (sshServer != null) sshServer.stop();
56 | sshServer = null;
57 | }
58 |
59 | }
60 |
--------------------------------------------------------------------------------
/src/main/java/de/gnmyt/mcdash/api/entities/BackupMode.java:
--------------------------------------------------------------------------------
1 | package de.gnmyt.mcdash.api.entities;
2 |
3 | public enum BackupMode {
4 |
5 | /**
6 | * Backups everything
7 | */
8 | SERVER(0),
9 |
10 | /**
11 | * Backups the worlds (only the world that are loaded)
12 | */
13 | WORLDS(1),
14 |
15 | /**
16 | * Backups the plugins directory
17 | */
18 | PLUGINS(2),
19 |
20 | /**
21 | * Backups the configs (e.g. the server.properties, bukkit.yml, etc.)
22 | */
23 | CONFIGS(3),
24 |
25 | /**
26 | * Backups the logs directory
27 | */
28 | LOGS(4);
29 |
30 | private final int mode;
31 |
32 | /**
33 | * Constructor of the {@link BackupMode}
34 | * @param mode The mode of the backup
35 | */
36 | BackupMode(int mode) {
37 | this.mode = mode;
38 | }
39 |
40 | /**
41 | * Gets the mode of the backup
42 | * @return the mode of the backup
43 | */
44 | public int getMode() {
45 | return mode;
46 | }
47 |
48 | /**
49 | * Gets the {@link BackupMode} from the given mode
50 | * @param mode The mode to get the {@link BackupMode} from
51 | * @return the {@link BackupMode} from the given mode
52 | */
53 | public static BackupMode fromMode(int mode) {
54 | for (BackupMode backupMode : values()) {
55 | if (backupMode.getMode() == mode) return backupMode;
56 | }
57 | return null;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/main/java/de/gnmyt/mcdash/api/entities/Schedule.java:
--------------------------------------------------------------------------------
1 | package de.gnmyt.mcdash.api.entities;
2 |
3 | import java.util.ArrayList;
4 |
5 | public class Schedule {
6 |
7 | private final String name;
8 | private final ScheduleExecution execution;
9 | private final ArrayList actions;
10 |
11 | /**
12 | * Constructor of the {@link Schedule}
13 | *
14 | * @param name The name of the schedule
15 | * @param execution The execution of the schedule
16 | * @param actions The actions of the schedule
17 | */
18 | public Schedule(String name, ScheduleExecution execution, ArrayList actions) {
19 | this.name = name;
20 | this.execution = execution;
21 | this.actions = actions;
22 | }
23 |
24 | /**
25 | * Gets the name of the schedule
26 | * @return the name of the schedule
27 | */
28 | public String getName() {
29 | return name;
30 | }
31 |
32 | /**
33 | * Gets the execution of the schedule
34 | * @return the execution of the schedule
35 | */
36 | public ScheduleExecution getExecution() {
37 | return execution;
38 | }
39 |
40 | /**
41 | * Gets the actions of the schedule
42 | * @return the actions of the schedule
43 | */
44 | public ArrayList getActions() {
45 | return actions;
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/de/gnmyt/mcdash/api/entities/ScheduleAction.java:
--------------------------------------------------------------------------------
1 | package de.gnmyt.mcdash.api.entities;
2 |
3 | public class ScheduleAction {
4 |
5 | private final ScheduleActionType type;
6 | private final String payload;
7 |
8 | /**
9 | * Constructor of the {@link ScheduleAction}
10 | * @param type The type of the action
11 | * @param payload The payload of the action
12 | */
13 | public ScheduleAction(ScheduleActionType type, String payload) {
14 | this.type = type;
15 | this.payload = payload;
16 | }
17 |
18 | /**
19 | * Constructor of the {@link ScheduleAction}
20 | *
21 | *
22 | * This constructor will set the payload to null
23 | * This means that the action type does not require a payload
24 | *
25 | *
26 | * @param type The type of the action
27 | */
28 | public ScheduleAction(ScheduleActionType type) {
29 | this(type, null);
30 | }
31 |
32 | /**
33 | * Gets the type of the action
34 | * @return the type of the action
35 | */
36 | public ScheduleActionType getType() {
37 | return type;
38 | }
39 |
40 | /**
41 | * Gets the payload of the action
42 | * @return the payload of the action
43 | */
44 | public String getPayload() {
45 | return payload;
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/src/main/java/de/gnmyt/mcdash/api/entities/ScheduleActionType.java:
--------------------------------------------------------------------------------
1 | package de.gnmyt.mcdash.api.entities;
2 |
3 | public enum ScheduleActionType {
4 |
5 | COMMAND(1, true),
6 | BROADCAST(2, true),
7 | RELOAD_SERVER(3, false),
8 | STOP_SERVER(4, false),
9 | CREATE_BACKUP(5, true),
10 | KICK_ALL_PLAYERS(6, true);
11 |
12 | private final int id;
13 | private final boolean requiresPayload;
14 |
15 | /**
16 | * Constructor of the {@link ScheduleActionType}
17 | * @param id The id of the action type
18 | * @param requiresPayload true
if the action type requires a payload, otherwise false
19 | */
20 | ScheduleActionType(int id, boolean requiresPayload) {
21 | this.id = id;
22 | this.requiresPayload = requiresPayload;
23 | }
24 |
25 | /**
26 | * Gets the id of the action type
27 | * @return the id of the action type
28 | */
29 | public int getId() {
30 | return id;
31 | }
32 |
33 | /**
34 | * Checks if the action type requires a payload
35 | * @return true
if the action type requires a payload, otherwise false
36 | */
37 | public boolean requiresPayload() {
38 | return requiresPayload;
39 | }
40 |
41 | /**
42 | * Gets the action type by its id
43 | * @param id The id of the action type
44 | * @return the action type
45 | */
46 | public static ScheduleActionType getById(int id) {
47 | for (ScheduleActionType type : values()) {
48 | if (type.getId() == id) return type;
49 | }
50 | return null;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/main/java/de/gnmyt/mcdash/api/entities/ScheduleFrequency.java:
--------------------------------------------------------------------------------
1 | package de.gnmyt.mcdash.api.entities;
2 |
3 | public enum ScheduleFrequency {
4 |
5 | MONTHLY,
6 | WEEKLY,
7 | DAILY,
8 | HOURLY
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/src/main/java/de/gnmyt/mcdash/api/handler/MultipartHandler.java:
--------------------------------------------------------------------------------
1 | package de.gnmyt.mcdash.api.handler;
2 |
3 | import com.sun.net.httpserver.HttpExchange;
4 | import de.gnmyt.mcdash.api.http.HTTPRequestContext;
5 | import de.gnmyt.mcdash.api.http.Request;
6 | import org.apache.commons.fileupload.disk.DiskFileItemFactory;
7 | import org.apache.commons.fileupload.servlet.ServletFileUpload;
8 |
9 | import java.util.Collections;
10 | import java.util.List;
11 |
12 | public abstract class MultipartHandler extends DefaultHandler {
13 |
14 | /**
15 | * The list of methods allowed in {@link MultipartHandler#prepareRequest}
16 | * @return the list of the allowed methods
17 | */
18 | public List multipartMethods() {
19 | return Collections.singletonList("PUT");
20 | }
21 |
22 | /**
23 | * The overridden method of {@link DefaultHandler#handle}
24 | * The method checks if the method is in the list of {@link MultipartHandler#multipartMethods} and puts the files into the request
25 | *
26 | * @param exchange The exchange you get from the handle function
27 | * @param writeBody Should the request body be written?
28 | * @return the new request
29 | */
30 | protected Request prepareRequest(HttpExchange exchange, boolean writeBody) {
31 |
32 | Request request = super.prepareRequest(exchange, !multipartMethods().contains(exchange.getRequestMethod()));
33 |
34 | if (multipartMethods().contains(exchange.getRequestMethod())) {
35 | try {
36 | request.setFiles(new ServletFileUpload(new DiskFileItemFactory()).parseRequest(new HTTPRequestContext(exchange)));
37 | } catch (Exception e) {
38 | e.printStackTrace();
39 | }
40 | }
41 |
42 | return request;
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/src/main/java/de/gnmyt/mcdash/api/handler/StaticHandler.java:
--------------------------------------------------------------------------------
1 | package de.gnmyt.mcdash.api.handler;
2 |
3 | import com.sun.net.httpserver.HttpExchange;
4 | import com.sun.net.httpserver.HttpHandler;
5 | import de.gnmyt.mcdash.api.http.ContentType;
6 |
7 | import java.io.IOException;
8 | import java.io.InputStream;
9 | import java.io.OutputStream;
10 |
11 | public class StaticHandler implements HttpHandler {
12 |
13 | /**
14 | * Handles the request of the client
15 | *
16 | * @param exchange the exchange containing the request from the
17 | * client and used to send the response
18 | * @throws IOException An exception that can occur while reading the request or writing the response
19 | */
20 | @Override
21 | public void handle(HttpExchange exchange) throws IOException {
22 | String path = exchange.getRequestURI().getPath();
23 | if (path.equals("/")) path = "/index.html";
24 |
25 | if (getResourceStream("webui" + path) == null) path = "/index.html";
26 |
27 | exchange.getResponseHeaders().add("Content-Type", ContentType.getContentType(path).getType());
28 |
29 | try (InputStream inputStream = getResourceStream("webui" + path)) {
30 | if (inputStream != null) {
31 | exchange.sendResponseHeaders(200, 0);
32 |
33 | try (OutputStream outputStream = exchange.getResponseBody()) {
34 | byte[] buffer = new byte[8192];
35 | int length;
36 | while ((length = inputStream.read(buffer)) != -1) outputStream.write(buffer, 0, length);
37 | }
38 | }
39 | }
40 |
41 | exchange.close();
42 | }
43 |
44 | /**
45 | * Gets the input stream of a resource
46 | *
47 | * @param path The path of the resource
48 | * @return the input stream of the resource
49 | */
50 | private InputStream getResourceStream(String path) {
51 | return getClass().getClassLoader().getResourceAsStream(path);
52 | }
53 |
54 | }
--------------------------------------------------------------------------------
/src/main/java/de/gnmyt/mcdash/api/http/ContentType.java:
--------------------------------------------------------------------------------
1 | package de.gnmyt.mcdash.api.http;
2 |
3 | /**
4 | * All content types needed by this plugin
5 | */
6 | public enum ContentType {
7 |
8 | /**
9 | * The default text content type
10 | */
11 | TEXT("text/html", "html"),
12 |
13 | /**
14 | * The json content type
15 | */
16 | JSON("application/json", "json"),
17 |
18 | /**
19 | * The multipart content type
20 | * Used by the {@link de.gnmyt.mcdash.api.handler.MultipartHandler}
21 | */
22 | MULTIPART("multipart/form-data", null),
23 |
24 | /**
25 | * The css content type
26 | */
27 | CSS("text/css", "css"),
28 |
29 | /**
30 | * The javascript content type
31 | */
32 | JAVASCRIPT("application/javascript", "js"),
33 |
34 | /**
35 | * The png content type
36 | */
37 | PNG("image/png", "png"),
38 |
39 | /**
40 | * The jpeg content type
41 | */
42 | JPEG("image/jpeg", "jpeg"),
43 |
44 | /**
45 | * The svg content type
46 | */
47 | SVG("image/svg+xml", "svg"),
48 |
49 | /**
50 | * The ico content type
51 | */
52 | ICO("image/x-icon", "ico"),
53 |
54 | /**
55 | * The woff2 content type
56 | */
57 | WOFF2("font/woff2", "woff2");
58 |
59 | private final String type;
60 | private final String fileEnding;
61 |
62 | /**
63 | * The basic constructor of the {@link ContentType}
64 | * @param type The content type (header value)
65 | */
66 | ContentType(String type, String fileEnding) {
67 | this.type = type;
68 | this.fileEnding = fileEnding;
69 | }
70 |
71 | public static ContentType getContentType(String fileEnding) {
72 | for (ContentType contentType : values()) {
73 | if (contentType.getFileEnding() != null && fileEnding.endsWith(contentType.getFileEnding()))
74 | return contentType;
75 | }
76 | return TEXT;
77 | }
78 |
79 | /**
80 | * Gets the content type
81 | * @return the content type
82 | */
83 | public String getType() {
84 | return type;
85 | }
86 |
87 | /**
88 | * Gets the file ending of the content type
89 | * @return the file ending of the content type
90 | */
91 | public String getFileEnding() {
92 | return fileEnding;
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/main/java/de/gnmyt/mcdash/api/http/HTTPMethod.java:
--------------------------------------------------------------------------------
1 | package de.gnmyt.mcdash.api.http;
2 |
3 | /**
4 | * All http methods needed by this plugin
5 | */
6 | public enum HTTPMethod {
7 |
8 | /**
9 | * The 'GET' request method. Used whenever a request wants to retrieve data
10 | */
11 | GET,
12 |
13 | /**
14 | * The 'POST' request method. Used whenever a request wants to change something on the server
15 | */
16 | POST,
17 |
18 | /**
19 | * The 'PUT' request method. Used whenever a request wants to add or upload something
20 | */
21 | PUT,
22 |
23 | /**
24 | * The 'DELETE' request method. Used whenever a request wants to delete a specific resource
25 | */
26 | DELETE,
27 |
28 | /**
29 | * The 'PATCH' request method. Used whenever a request wants to apply partial modifications to a resource
30 | */
31 | PATCH,
32 |
33 | /**
34 | * The 'OPTIONS' request method. Used whenever the request wants to describe the communication options for the target resource
35 | */
36 | OPTIONS
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/src/main/java/de/gnmyt/mcdash/api/http/HTTPRequestContext.java:
--------------------------------------------------------------------------------
1 | package de.gnmyt.mcdash.api.http;
2 |
3 | import com.sun.net.httpserver.HttpExchange;
4 | import org.apache.commons.fileupload.RequestContext;
5 |
6 | import java.io.InputStream;
7 |
8 | public class HTTPRequestContext implements RequestContext {
9 |
10 | private HttpExchange exchange;
11 |
12 | /**
13 | * Basic constructor of the {@link HTTPRequestContext}
14 | * @param exchange The exchange provided from the {@link de.gnmyt.mcdash.api.handler.DefaultHandler#handle} method
15 | */
16 | public HTTPRequestContext(HttpExchange exchange) {
17 | this.exchange = exchange;
18 | }
19 |
20 | /**
21 | * Gets the default character encoding
22 | * @return the default character encoding
23 | */
24 | @Override
25 | public String getCharacterEncoding() {
26 | return "UTF-8";
27 | }
28 |
29 | /**
30 | * Gets the content type from the exchange
31 | * @return the content type from the exchange
32 | */
33 | @Override
34 | public String getContentType() {
35 | return exchange.getRequestHeaders().getFirst("Content-Type");
36 | }
37 |
38 | /**
39 | * Gets the default content length
40 | * @return the default content length
41 | */
42 | @Override
43 | public int getContentLength() {
44 | return 0;
45 | }
46 |
47 | /**
48 | * Gets the input stream from the exchange
49 | * @return the input stream from the exchange
50 | */
51 | @Override
52 | public InputStream getInputStream() {
53 | return exchange.getRequestBody();
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/main/java/de/gnmyt/mcdash/api/json/ArrayBuilder.java:
--------------------------------------------------------------------------------
1 | package de.gnmyt.mcdash.api.json;
2 |
3 | import com.fasterxml.jackson.core.JsonProcessingException;
4 | import com.fasterxml.jackson.databind.ObjectMapper;
5 | import com.fasterxml.jackson.databind.node.ArrayNode;
6 | import com.fasterxml.jackson.databind.node.ObjectNode;
7 |
8 | public class ArrayBuilder {
9 |
10 | private ObjectMapper mapper = new ObjectMapper();
11 |
12 | private ArrayNode list = mapper.createArrayNode();
13 |
14 | /**
15 | * Starts a node builder
16 | * @return the created {@link NodeBuilder}
17 | */
18 | public NodeBuilder addNode() {
19 | return new NodeBuilder(this);
20 | }
21 |
22 | /**
23 | * Removes a object node from the array list
24 | * @param index The node you want to add
25 | * @return the current {@link ArrayBuilder} instance
26 | */
27 | public ArrayBuilder remove(int index) {
28 | list.remove(index);
29 | return this;
30 | }
31 |
32 | /**
33 | * Adds a object node to the array list
34 | * @param node The node you want to add
35 | * @return the current {@link ArrayBuilder} instance
36 | */
37 | public ArrayBuilder add(ObjectNode node) {
38 | list.add(node);
39 | return this;
40 | }
41 |
42 | /**
43 | * Gets the current object mapper
44 | * @return the current object mapper
45 | */
46 | public ObjectMapper getMapper() {
47 | return mapper;
48 | }
49 |
50 | /**
51 | * Gets the json response as a string
52 | * @return the json string
53 | */
54 | public String toJSON() {
55 | try {
56 | return mapper.writer().writeValueAsString(list);
57 | } catch (JsonProcessingException e) {
58 | e.printStackTrace();
59 | return "{}";
60 | }
61 | }
62 |
63 | /**
64 | * Gets the json response as a pretty printed string
65 | * @return the json string
66 | */
67 | public String toPrettyJSON() {
68 | try {
69 | return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(list);
70 | } catch (JsonProcessingException e) {
71 | e.printStackTrace();
72 | return "{\n}";
73 | }
74 | }
75 |
76 | }
77 |
--------------------------------------------------------------------------------
/src/main/java/de/gnmyt/mcdash/api/ssh/MCShellFactory.java:
--------------------------------------------------------------------------------
1 | package de.gnmyt.mcdash.api.ssh;
2 |
3 | import org.apache.sshd.server.channel.ChannelSession;
4 | import org.apache.sshd.server.command.Command;
5 | import org.apache.sshd.server.shell.ShellFactory;
6 |
7 | import java.io.IOException;
8 |
9 | public class MCShellFactory implements ShellFactory {
10 |
11 | /**
12 | * Creates a new {@link MCCommand} instance
13 | * @param channelSession The current {@link ChannelSession}
14 | * @return the created {@link MCCommand} instance
15 | * @throws IOException Will be thrown if the command could not be created
16 | */
17 | @Override
18 | public Command createShell(ChannelSession channelSession) throws IOException {
19 | return new MCCommand();
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/de/gnmyt/mcdash/api/ssh/SSHAuthenticator.java:
--------------------------------------------------------------------------------
1 | package de.gnmyt.mcdash.api.ssh;
2 |
3 | import de.gnmyt.mcdash.MinecraftDashboard;
4 | import de.gnmyt.mcdash.api.config.AccountManager;
5 | import org.apache.sshd.server.auth.AsyncAuthException;
6 | import org.apache.sshd.server.auth.password.PasswordAuthenticator;
7 | import org.apache.sshd.server.auth.password.PasswordChangeRequiredException;
8 | import org.apache.sshd.server.session.ServerSession;
9 |
10 | public class SSHAuthenticator implements PasswordAuthenticator {
11 |
12 | private final AccountManager accountManager = MinecraftDashboard.getAccountManager();
13 |
14 | /**
15 | * Checks if the given username and password are valid
16 | * @param username The username of the user
17 | * @param password The password of the user
18 | * @param serverSession The current {@link ServerSession}
19 | * @return true
if the username and password are valid, otherwise false
20 | * @throws PasswordChangeRequiredException Will be thrown if the password needs to be changed
21 | * @throws AsyncAuthException Will be thrown if the authentication is not finished yet
22 | */
23 | @Override
24 | public boolean authenticate(String username, String password, ServerSession serverSession) throws PasswordChangeRequiredException, AsyncAuthException {
25 | return accountManager.isValidPassword(username, password);
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/de/gnmyt/mcdash/api/tasks/TPSRunnable.java:
--------------------------------------------------------------------------------
1 | package de.gnmyt.mcdash.api.tasks;
2 |
3 | public class TPSRunnable implements Runnable {
4 |
5 | public int tick_count = 0;
6 | public long[] ticks = new long[600];
7 |
8 | /**
9 | * Updates the tick variables
10 | */
11 | @Override
12 | public void run() {
13 | ticks[(tick_count % ticks.length)] = System.currentTimeMillis();
14 | tick_count++;
15 | }
16 |
17 | /**
18 | * Gets the current tps of the server
19 | * @return the current tps of the server
20 | */
21 | public double getCurrentTPS() {
22 | return getCurrentTPS(100);
23 | }
24 |
25 | /**
26 | * Gets the current tps of the server
27 | * @param ticks The amount of ticks
28 | * @return the current tps of the server
29 | */
30 | public double getCurrentTPS(int ticks) {
31 | try {
32 | if (tick_count< ticks) return 20.0D;
33 |
34 | int target = (tick_count-ticks-1) % this.ticks.length;
35 | long elapsed = System.currentTimeMillis() - this.ticks[target];
36 |
37 | return ticks / (elapsed / 1000.0D);
38 | } catch (Exception e) {
39 | return 20.0D;
40 | }
41 | }
42 |
43 | /**
44 | * Gets the tps of the server (rounded)
45 | * @return the tps of the server (rounded)
46 | */
47 | public long getCurrentRoundedTPS() {
48 | return Math.round(getCurrentTPS());
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/src/main/java/de/gnmyt/mcdash/commands/PasswordCommand.java:
--------------------------------------------------------------------------------
1 | package de.gnmyt.mcdash.commands;
2 |
3 | import de.gnmyt.mcdash.api.config.AccountManager;
4 | import org.bukkit.command.Command;
5 | import org.bukkit.command.CommandExecutor;
6 | import org.bukkit.command.CommandSender;
7 |
8 | public class PasswordCommand implements CommandExecutor {
9 |
10 | private final AccountManager accountManager;
11 |
12 | /**
13 | * Constructor of the {@link PasswordCommand}
14 | * @param accountManager The account manager of the plugin
15 | */
16 | public PasswordCommand(AccountManager accountManager) {
17 | this.accountManager = accountManager;
18 | }
19 |
20 | @Override
21 | public boolean onCommand(CommandSender sender, Command command, String s, String[] args) {
22 | if (!sender.hasPermission("mcdash.use")) {
23 | sender.sendMessage("§cYou don't have the permission to do that");
24 | return false;
25 | }
26 |
27 | if (args.length == 1) {
28 | accountManager.register(sender.getName(), args[0]);
29 | sender.sendMessage("§aYour password has been changed successfully");
30 | } else {
31 | sender.sendMessage("§cPlease use /panel ");
32 | }
33 |
34 | return false;
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/de/gnmyt/mcdash/panel/routes/DefaultRoute.java:
--------------------------------------------------------------------------------
1 | package de.gnmyt.mcdash.panel.routes;
2 |
3 | import de.gnmyt.mcdash.MinecraftDashboard;
4 | import de.gnmyt.mcdash.api.handler.DefaultHandler;
5 | import de.gnmyt.mcdash.api.http.Request;
6 | import de.gnmyt.mcdash.api.http.ResponseController;
7 |
8 | public class DefaultRoute extends DefaultHandler {
9 |
10 | /**
11 | * The default/root route
12 | * @param request The request object from the HttpExchange
13 | * @param response The response controller from the HttpExchange
14 | */
15 | @Override
16 | public void get(Request request, ResponseController response) throws Exception {
17 | response.text("Minecraft Dashboard by "+ MinecraftDashboard.getInstance().getDescription().getAuthors().get(0));
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/de/gnmyt/mcdash/panel/routes/PingRoute.java:
--------------------------------------------------------------------------------
1 | package de.gnmyt.mcdash.panel.routes;
2 |
3 | import de.gnmyt.mcdash.api.handler.DefaultHandler;
4 | import de.gnmyt.mcdash.api.http.Request;
5 | import de.gnmyt.mcdash.api.http.ResponseController;
6 |
7 | public class PingRoute extends DefaultHandler {
8 |
9 | @Override
10 | public String path() {
11 | return "ping";
12 | }
13 |
14 | /**
15 | * Simple ping route
16 | * @param request The request object from the HttpExchange
17 | * @param response The response controller from the HttpExchange
18 | */
19 | @Override
20 | public void get(Request request, ResponseController response) {
21 | response.text("Pong!");
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/de/gnmyt/mcdash/panel/routes/UpdateRoute.java:
--------------------------------------------------------------------------------
1 | package de.gnmyt.mcdash.panel.routes;
2 |
3 | import de.gnmyt.mcdash.MinecraftDashboard;
4 | import de.gnmyt.mcdash.api.handler.DefaultHandler;
5 | import de.gnmyt.mcdash.api.http.Request;
6 | import de.gnmyt.mcdash.api.http.ResponseController;
7 |
8 | public class UpdateRoute extends DefaultHandler {
9 |
10 | @Override
11 | public String path() {
12 | return "update";
13 | }
14 |
15 | @Override
16 | public void get(Request request, ResponseController response) throws Exception {
17 | response.json("available=" + !MinecraftDashboard.getUpdateManager().isLatestVersion(),
18 | "latest=\"" + MinecraftDashboard.getUpdateManager().getLatestVersion() + "\"",
19 | "current=\"" + MinecraftDashboard.getUpdateManager().getCurrentVersion() + "\"");
20 | }
21 |
22 | @Override
23 | public void post(Request request, ResponseController response) throws Exception {
24 | if (MinecraftDashboard.getUpdateManager().isLatestVersion()) {
25 | response.code(400).message("The current version is already the latest version");
26 | return;
27 | }
28 |
29 | boolean reloadAfterUpdate = request.getBody().containsKey("reloadAfterUpdate")
30 | ? getBooleanFromBody(request, "reloadAfterUpdate") : false;
31 |
32 | MinecraftDashboard.getUpdateManager().update(reloadAfterUpdate);
33 |
34 | response.message("The update has been installed successfully");
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/de/gnmyt/mcdash/panel/routes/action/ReloadRoute.java:
--------------------------------------------------------------------------------
1 | package de.gnmyt.mcdash.panel.routes.action;
2 |
3 | import de.gnmyt.mcdash.api.handler.DefaultHandler;
4 | import de.gnmyt.mcdash.api.http.Request;
5 | import de.gnmyt.mcdash.api.http.ResponseController;
6 | import org.bukkit.Bukkit;
7 |
8 | public class ReloadRoute extends DefaultHandler {
9 |
10 | @Override
11 | public String path() {
12 | return "reload";
13 | }
14 |
15 | /**
16 | * Reloads the server
17 | * @param request The request object from the HttpExchange
18 | * @param response The response controller from the HttpExchange
19 | */
20 | @Override
21 | public void post(Request request, ResponseController response) throws Exception {
22 | response.message("Action executed.");
23 |
24 | runSync(Bukkit::reload);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/de/gnmyt/mcdash/panel/routes/action/ShutdownRoute.java:
--------------------------------------------------------------------------------
1 | package de.gnmyt.mcdash.panel.routes.action;
2 |
3 | import de.gnmyt.mcdash.api.handler.DefaultHandler;
4 | import de.gnmyt.mcdash.api.http.Request;
5 | import de.gnmyt.mcdash.api.http.ResponseController;
6 | import org.bukkit.Bukkit;
7 |
8 | public class ShutdownRoute extends DefaultHandler {
9 |
10 | @Override
11 | public String path() {
12 | return "shutdown";
13 | }
14 |
15 | /**
16 | * Shuts down the server
17 | * @param request The request object from the HttpExchange
18 | * @param response The response controller from the HttpExchange
19 | */
20 | @Override
21 | public void post(Request request, ResponseController response) throws Exception {
22 | response.message("Action executed.");
23 |
24 | runSync(Bukkit::shutdown);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/de/gnmyt/mcdash/panel/routes/action/WhitelistRoute.java:
--------------------------------------------------------------------------------
1 | package de.gnmyt.mcdash.panel.routes.action;
2 |
3 | import de.gnmyt.mcdash.api.handler.DefaultHandler;
4 | import de.gnmyt.mcdash.api.http.Request;
5 | import de.gnmyt.mcdash.api.http.ResponseController;
6 | import org.bukkit.Bukkit;
7 |
8 | public class WhitelistRoute extends DefaultHandler {
9 |
10 | @Override
11 | public String path() {
12 | return "whitelist";
13 | }
14 |
15 | /**
16 | * Gets the current whitelist status
17 | * @param request The request object from the HttpExchange
18 | * @param response The response controller from the HttpExchange
19 | */
20 | @Override
21 | public void get(Request request, ResponseController response) throws Exception {
22 | response.json("status="+Bukkit.hasWhitelist());
23 | }
24 |
25 | /**
26 | * Updates the current whitelist status
27 | * @param request The request object from the HttpExchange
28 | * @param response The response controller from the HttpExchange
29 | */
30 | @Override
31 | public void patch(Request request, ResponseController response) throws Exception {
32 | if (!isBooleanInBody(request, response, "status")) return;
33 |
34 | runSync(() -> {
35 | Bukkit.setWhitelist(getBooleanFromBody(request, "status"));
36 | response.message("Whitelist successfully " + (getBooleanFromBody(request, "status") ? "enabled" : "disabled"));
37 | });
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/de/gnmyt/mcdash/panel/routes/backups/BackupDownloadRoute.java:
--------------------------------------------------------------------------------
1 | package de.gnmyt.mcdash.panel.routes.backups;
2 |
3 | import de.gnmyt.mcdash.MinecraftDashboard;
4 | import de.gnmyt.mcdash.api.controller.BackupController;
5 | import de.gnmyt.mcdash.api.handler.DefaultHandler;
6 | import de.gnmyt.mcdash.api.http.Request;
7 | import de.gnmyt.mcdash.api.http.ResponseController;
8 | import org.apache.commons.io.FileUtils;
9 |
10 | public class BackupDownloadRoute extends DefaultHandler {
11 |
12 | private final BackupController controller = MinecraftDashboard.getBackupController();
13 |
14 | @Override
15 | public String path() {
16 | return "download";
17 | }
18 |
19 | /**
20 | * Downloads a backup
21 | * @param request The request object from the HttpExchange
22 | * @param response The response controller from the HttpExchange
23 | * @throws Exception An exception that can occur while executing the code
24 | */
25 | @Override
26 | public void get(Request request, ResponseController response) throws Exception {
27 | if (!isStringInQuery(request, response, "backup_id")) return;
28 |
29 | String backupId = getStringFromQuery(request, "backup_id");
30 |
31 | if (!controller.backupExists(backupId)) {
32 | response.code(404).message("Backup not found");
33 | return;
34 | }
35 |
36 | response.header("Content-Disposition", "attachment; filename=Backup.zip");
37 |
38 | response.bytes(FileUtils.readFileToByteArray(controller.getBackup(backupId)));
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/de/gnmyt/mcdash/panel/routes/backups/BackupRestoreRoute.java:
--------------------------------------------------------------------------------
1 | package de.gnmyt.mcdash.panel.routes.backups;
2 |
3 | import de.gnmyt.mcdash.MinecraftDashboard;
4 | import de.gnmyt.mcdash.api.controller.BackupController;
5 | import de.gnmyt.mcdash.api.handler.DefaultHandler;
6 | import de.gnmyt.mcdash.api.http.Request;
7 | import de.gnmyt.mcdash.api.http.ResponseController;
8 |
9 | public class BackupRestoreRoute extends DefaultHandler {
10 |
11 | private final BackupController controller = MinecraftDashboard.getBackupController();
12 |
13 | @Override
14 | public String path() {
15 | return "restore";
16 | }
17 |
18 | /**
19 | * Restores a backup
20 | * @param request The request object from the HttpExchange
21 | * @param response The response controller from the HttpExchange
22 | * @throws Exception An exception that can occur while executing the code
23 | */
24 | @Override
25 | public void post(Request request, ResponseController response) throws Exception {
26 | if (!isStringInBody(request, response, "backup_id")) return;
27 | if (!isBooleanInBody(request, response, "halt")) return;
28 |
29 | String backupId = getStringFromBody(request, "backup_id");
30 | boolean restart = getBooleanFromBody(request, "halt");
31 |
32 | if (!controller.backupExists(backupId)) {
33 | response.code(404).message("Backup not found");
34 | return;
35 | }
36 |
37 | controller.restoreBackup(backupId, restart);
38 | response.message("Backup restored");
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/main/java/de/gnmyt/mcdash/panel/routes/players/KickRoute.java:
--------------------------------------------------------------------------------
1 | package de.gnmyt.mcdash.panel.routes.players;
2 |
3 | import de.gnmyt.mcdash.api.handler.DefaultHandler;
4 | import de.gnmyt.mcdash.api.http.Request;
5 | import de.gnmyt.mcdash.api.http.ResponseController;
6 | import org.bukkit.Bukkit;
7 |
8 | public class KickRoute extends DefaultHandler {
9 |
10 | @Override
11 | public String path() {
12 | return "kick";
13 | }
14 |
15 | /**
16 | * Kicks a player from the server
17 | * @param request The request object from the HttpExchange
18 | * @param response The response controller from the HttpExchange
19 | */
20 | @Override
21 | public void post(Request request, ResponseController response) throws Exception {
22 | if (!isStringInBody(request, response, "username")) return;
23 |
24 | String username = getStringFromBody(request, "username");
25 | String reason = getStringFromBody(request, "reason") != null ? getStringFromBody(request, "reason") : "";
26 |
27 | if (Bukkit.getPlayer(username) != null) {
28 | runSync(() -> Bukkit.getPlayer(username).kickPlayer(reason));
29 | } else {
30 | response.code(404).message("Player not found");
31 | return;
32 | }
33 |
34 | response.message("Successfully kicked the player");
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/src/main/java/de/gnmyt/mcdash/panel/routes/players/OnlineRoute.java:
--------------------------------------------------------------------------------
1 | package de.gnmyt.mcdash.panel.routes.players;
2 |
3 | import de.gnmyt.mcdash.api.handler.DefaultHandler;
4 | import de.gnmyt.mcdash.api.http.ContentType;
5 | import de.gnmyt.mcdash.api.http.Request;
6 | import de.gnmyt.mcdash.api.http.ResponseController;
7 | import de.gnmyt.mcdash.api.json.ArrayBuilder;
8 | import org.bukkit.Bukkit;
9 | import org.bukkit.Statistic;
10 | import org.bukkit.entity.Player;
11 |
12 | public class OnlineRoute extends DefaultHandler {
13 |
14 | @Override
15 | public String path() {
16 | return "online";
17 | }
18 |
19 | /**
20 | * Gets all current online players
21 | * @param request The request object from the HttpExchange
22 | * @param response The response controller from the HttpExchange
23 | */
24 | @Override
25 | public void get(Request request, ResponseController response) throws Exception {
26 |
27 | ArrayBuilder builder = new ArrayBuilder();
28 |
29 | Statistic playStat;
30 | try {
31 | playStat = Statistic.valueOf("PLAY_ONE_TICK"); // used below MC 1.15.2
32 | } catch (IllegalArgumentException ignored) {
33 | playStat = Statistic.valueOf("PLAY_ONE_MINUTE"); // MC 1.15.2 and above
34 | }
35 |
36 | for (Player player : Bukkit.getOnlinePlayers()) {
37 | builder.addNode()
38 | .add("uuid", player.getUniqueId().toString())
39 | .add("name", player.getName())
40 | .add("player_time", player.getStatistic(playStat))
41 | .add("current_world", player.getWorld().getName())
42 | .add("address", player.getAddress().getHostName())
43 | .add("health", Math.round(player.getHealth()))
44 | .add("food_level", Math.round(player.getFoodLevel()))
45 | .add("game_mode", player.getGameMode().name())
46 | .add("is_op", player.isOp())
47 | .register();
48 | }
49 |
50 | response.type(ContentType.JSON).text(builder.toJSON());
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/main/java/de/gnmyt/mcdash/panel/routes/players/OpRoute.java:
--------------------------------------------------------------------------------
1 | package de.gnmyt.mcdash.panel.routes.players;
2 |
3 | import de.gnmyt.mcdash.api.handler.DefaultHandler;
4 | import de.gnmyt.mcdash.api.http.Request;
5 | import de.gnmyt.mcdash.api.http.ResponseController;
6 | import org.bukkit.Bukkit;
7 | import org.bukkit.entity.Player;
8 |
9 | public class OpRoute extends DefaultHandler {
10 |
11 | @Override
12 | public String path() {
13 | return "op";
14 | }
15 |
16 | private Player getPlayer(Request request, ResponseController response) {
17 | if (!isStringInBody(request, response, "username")) return null;
18 |
19 | String username = getStringFromBody(request, "username");
20 | Player player = Bukkit.getPlayer(username);
21 |
22 | if (player == null) {
23 | response.code(400).message("The player is not online");
24 | return null;
25 | }
26 |
27 | return player;
28 | }
29 |
30 | @Override
31 | public void put(Request request, ResponseController response) throws Exception {
32 | Player player = getPlayer(request, response);
33 | if (player == null) return;
34 |
35 | runSync(() -> player.setOp(true));
36 | response.message("The player is now op");
37 | }
38 |
39 | @Override
40 | public void delete(Request request, ResponseController response) throws Exception {
41 | Player player = getPlayer(request, response);
42 | if (player == null) return;
43 |
44 | runSync(() -> player.setOp(false));
45 | response.message("The player is no longer op");
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/java/de/gnmyt/mcdash/panel/routes/players/TeleportRoute.java:
--------------------------------------------------------------------------------
1 | package de.gnmyt.mcdash.panel.routes.players;
2 |
3 | import de.gnmyt.mcdash.api.handler.DefaultHandler;
4 | import de.gnmyt.mcdash.api.http.Request;
5 | import de.gnmyt.mcdash.api.http.ResponseController;
6 | import org.bukkit.Bukkit;
7 |
8 | public class TeleportRoute extends DefaultHandler {
9 |
10 | @Override
11 | public String path() {
12 | return "tp";
13 | }
14 |
15 | /**
16 | * Teleports a player to a specific world
17 | * @param request The request object from the HttpExchange
18 | * @param response The response controller from the HttpExchange
19 | * @throws Exception An exception that can be thrown
20 | */
21 | @Override
22 | public void post(Request request, ResponseController response) throws Exception {
23 | if (!isStringInBody(request, response, "username")) return;
24 | if (!isStringInBody(request, response, "world")) return;
25 |
26 | String player = getStringFromBody(request, "username");
27 | String world = getStringFromBody(request, "world");
28 |
29 | if (Bukkit.getPlayer(player) == null) {
30 | response.code(400).message("The player " + player + " is not online");
31 | return;
32 | }
33 |
34 | runSync(() -> Bukkit.getPlayer(player).teleport(Bukkit.getWorld(world).getSpawnLocation()));
35 |
36 | response.message("Successfully teleported the player " + player + " to the world " + world);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/main/java/de/gnmyt/mcdash/panel/routes/plugin/PluginListRoute.java:
--------------------------------------------------------------------------------
1 | package de.gnmyt.mcdash.panel.routes.plugin;
2 |
3 | import de.gnmyt.mcdash.api.handler.DefaultHandler;
4 | import de.gnmyt.mcdash.api.http.ContentType;
5 | import de.gnmyt.mcdash.api.http.Request;
6 | import de.gnmyt.mcdash.api.http.ResponseController;
7 | import de.gnmyt.mcdash.api.json.ArrayBuilder;
8 | import org.bukkit.Bukkit;
9 | import org.bukkit.plugin.Plugin;
10 |
11 | import java.io.File;
12 |
13 | public class PluginListRoute extends DefaultHandler {
14 |
15 | @Override
16 | public String path() {
17 | return "list";
18 | }
19 |
20 | /**
21 | * Gets all plugins of the server
22 | * @param request The request object from the HttpExchange
23 | * @param response The response controller from the HttpExchange
24 | */
25 | @Override
26 | public void get(Request request, ResponseController response) throws Exception {
27 |
28 | ArrayBuilder builder = new ArrayBuilder();
29 |
30 | for (Plugin plugin : Bukkit.getPluginManager().getPlugins()) {
31 | if (plugin.getClass().getProtectionDomain().getCodeSource() == null) continue;
32 | String pluginJar = plugin.getClass().getProtectionDomain().getCodeSource().getLocation().getPath();
33 | String pluginJarNoPath = pluginJar.substring(pluginJar.lastIndexOf(File.separator) + 1);
34 |
35 | if (!new File("./plugins/" + pluginJarNoPath).exists()) continue;
36 |
37 | builder.addNode()
38 | .add("name", plugin.getName())
39 | .add("author", plugin.getDescription().getAuthors().size() == 0 ? null : plugin.getDescription().getAuthors().get(0))
40 | .add("description", plugin.getDescription().getDescription())
41 | .add("path", pluginJarNoPath)
42 | .add("enabled", plugin.isEnabled())
43 | .add("version", plugin.getDescription().getVersion())
44 | .register();
45 | }
46 |
47 | response.type(ContentType.JSON).text(builder.toJSON());
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/src/main/java/de/gnmyt/mcdash/panel/routes/schedules/ScheduleNameRoute.java:
--------------------------------------------------------------------------------
1 | package de.gnmyt.mcdash.panel.routes.schedules;
2 |
3 | import de.gnmyt.mcdash.MinecraftDashboard;
4 | import de.gnmyt.mcdash.api.config.ScheduleManager;
5 | import de.gnmyt.mcdash.api.handler.DefaultHandler;
6 | import de.gnmyt.mcdash.api.http.Request;
7 | import de.gnmyt.mcdash.api.http.ResponseController;
8 |
9 | public class ScheduleNameRoute extends DefaultHandler {
10 |
11 | private final ScheduleManager scheduleManager = MinecraftDashboard.getScheduleManager();
12 |
13 | @Override
14 | public String path() {
15 | return "name";
16 | }
17 |
18 | /**
19 | * Renames a schedule
20 | *
21 | * @param request The request object from the HttpExchange
22 | * @param response The response controller from the HttpExchange
23 | */
24 | @Override
25 | public void patch(Request request, ResponseController response) throws Exception {
26 | if (!isStringInBody(request, response, "name")) return;
27 | if (!isStringInBody(request, response, "new_name")) return;
28 |
29 | String name = getStringFromBody(request, "name");
30 | String new_name = getStringFromBody(request, "new_name");
31 |
32 | if (scheduleManager.getScheduleByName(name) == null) {
33 | response.code(404).message("The schedule does not exist");
34 | return;
35 | }
36 |
37 | if (scheduleManager.getScheduleByName(new_name) != null) {
38 | response.code(400).message("The new name is already in use");
39 | return;
40 | }
41 |
42 | scheduleManager.renameSchedule(name, new_name);
43 | response.message("The schedule has been renamed");
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/src/main/java/de/gnmyt/mcdash/panel/routes/services/SSHRoute.java:
--------------------------------------------------------------------------------
1 | package de.gnmyt.mcdash.panel.routes.services;
2 |
3 | import de.gnmyt.mcdash.MinecraftDashboard;
4 | import de.gnmyt.mcdash.api.config.SSHManager;
5 | import de.gnmyt.mcdash.api.handler.DefaultHandler;
6 | import de.gnmyt.mcdash.api.http.Request;
7 | import de.gnmyt.mcdash.api.http.ResponseController;
8 |
9 | public class SSHRoute extends DefaultHandler {
10 |
11 | private final SSHManager sshManager = MinecraftDashboard.getSSHManager();
12 |
13 | @Override
14 | public String path() {
15 | return "ssh";
16 | }
17 |
18 | /**
19 | * Gets the current SSH settings
20 | * @param request The request object from the HttpExchange
21 | * @param response The response controller from the HttpExchange
22 | * @throws Exception if an error occurred
23 | */
24 | @Override
25 | public void get(Request request, ResponseController response) throws Exception {
26 | response.json("enabled=" + sshManager.isSSHEnabled(), "port=" + sshManager.getSSHPort());
27 | }
28 |
29 | /**
30 | * Updates the SSH settings
31 | * @param request The request object from the HttpExchange
32 | * @param response The response controller from the HttpExchange
33 | * @throws Exception if an error occurred
34 | */
35 | @Override
36 | public void patch(Request request, ResponseController response) throws Exception {
37 | boolean enabled = getStringFromBody(request, "enabled") != null ? getBooleanFromBody(request, "enabled") : sshManager.isSSHEnabled();
38 | int port = getStringFromBody(request, "port") != null ? getIntegerFromBody(request, "port") : sshManager.getSSHPort();
39 |
40 | if (sshManager.getSSHPort() != port) sshManager.setSSHPort(port);
41 | if (sshManager.isSSHEnabled() != enabled) sshManager.updateStatus(enabled);
42 |
43 | response.message("Successfully updated the ssh settings");
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/main/java/de/gnmyt/mcdash/panel/routes/stats/StatsRoute.java:
--------------------------------------------------------------------------------
1 | package de.gnmyt.mcdash.panel.routes.stats;
2 |
3 | import de.gnmyt.mcdash.MinecraftDashboard;
4 | import de.gnmyt.mcdash.api.controller.StatsController;
5 | import de.gnmyt.mcdash.api.handler.DefaultHandler;
6 | import de.gnmyt.mcdash.api.http.Request;
7 | import de.gnmyt.mcdash.api.http.ResponseController;
8 | import org.bukkit.Bukkit;
9 |
10 | public class StatsRoute extends DefaultHandler {
11 |
12 | private final StatsController STATS = new StatsController(MinecraftDashboard.getInstance());
13 |
14 | /**
15 | * Gets the current server statistics such as the tps, processors, memory and the space
16 | * @param request The request object from the HttpExchange
17 | * @param response The response controller from the HttpExchange
18 | */
19 | @Override
20 | public void get(Request request, ResponseController response) {
21 | response.json("tps="+STATS.getTPS(), "processors="+STATS.getAvailableProcessors(),
22 | "free_memory="+STATS.getFreeMemory(), "total_memory="+STATS.getTotalMemory(), "used_memory="+STATS.getUsedMemory(),
23 | "free_space="+STATS.getFreeSpace(), "total_space="+STATS.getTotalSpace(), "used_space="+STATS.getUsedSpace(),
24 | "max_players="+ Bukkit.getMaxPlayers(), "online_players="+Bukkit.getOnlinePlayers().size());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/main/java/de/gnmyt/mcdash/panel/routes/worlds/DifficultyRoute.java:
--------------------------------------------------------------------------------
1 | package de.gnmyt.mcdash.panel.routes.worlds;
2 |
3 | import de.gnmyt.mcdash.api.handler.DefaultHandler;
4 | import de.gnmyt.mcdash.api.http.Request;
5 | import de.gnmyt.mcdash.api.http.ResponseController;
6 | import org.bukkit.Bukkit;
7 |
8 | public class DifficultyRoute extends DefaultHandler {
9 |
10 | @Override
11 | public String path() {
12 | return "difficulty";
13 | }
14 |
15 | /**
16 | * Changes the difficulty of a specific world
17 | * @param request The request object from the HttpExchange
18 | * @param response The response controller from the HttpExchange
19 | * @throws Exception An exception that can occur while executing the code
20 | */
21 | @Override
22 | public void patch(Request request, ResponseController response) throws Exception {
23 | if (!isStringInBody(request, response, "difficulty")) return;
24 | if (!isStringInBody(request, response, "world")) return;
25 |
26 | String difficulty = getStringFromBody(request, "difficulty");
27 | String world = getStringFromBody(request, "world");
28 |
29 | if (difficulty.equalsIgnoreCase("peaceful")) {
30 | runSync(() -> Bukkit.getWorld(world).setDifficulty(org.bukkit.Difficulty.PEACEFUL));
31 | } else if (difficulty.equalsIgnoreCase("easy")) {
32 | runSync(() -> Bukkit.getWorld(world).setDifficulty(org.bukkit.Difficulty.EASY));
33 | } else if (difficulty.equalsIgnoreCase("normal")) {
34 | runSync(() -> Bukkit.getWorld(world).setDifficulty(org.bukkit.Difficulty.NORMAL));
35 | } else if (difficulty.equalsIgnoreCase("hard")) {
36 | runSync(() -> Bukkit.getWorld(world).setDifficulty(org.bukkit.Difficulty.HARD));
37 | } else {
38 | response.code(400).message("The difficulty must be 'peaceful', 'easy', 'normal' or 'hard'");
39 | }
40 |
41 | response.message("Successfully updated the difficulty of the world " + world);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/java/de/gnmyt/mcdash/panel/routes/worlds/TimeRoute.java:
--------------------------------------------------------------------------------
1 | package de.gnmyt.mcdash.panel.routes.worlds;
2 |
3 | import de.gnmyt.mcdash.api.handler.DefaultHandler;
4 | import de.gnmyt.mcdash.api.http.Request;
5 | import de.gnmyt.mcdash.api.http.ResponseController;
6 | import org.bukkit.Bukkit;
7 |
8 | public class TimeRoute extends DefaultHandler {
9 |
10 | @Override
11 | public String path() {
12 | return "time";
13 | }
14 |
15 | /**
16 | * Changes the time of a specific world
17 | * @param request The request object from the HttpExchange
18 | * @param response The response controller from the HttpExchange
19 | * @throws Exception An exception that can be thrown
20 | */
21 | @Override
22 | public void patch(Request request, ResponseController response) throws Exception {
23 | if (!isIntegerInBody(request, response, "time")) return;
24 | if (!isStringInBody(request, response, "world")) return;
25 |
26 | int time = getIntegerFromBody(request, "time");
27 | String world = getStringFromBody(request, "world");
28 |
29 | if (time < 0 || time > 24000) {
30 | response.code(400).message("The time must be between 0 and 24000");
31 | return;
32 | }
33 |
34 | runSync(() -> Bukkit.getWorld(world).setTime(time));
35 |
36 | response.message("Successfully updated the time of the world " + world);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/main/java/de/gnmyt/mcdash/panel/routes/worlds/WeatherRoute.java:
--------------------------------------------------------------------------------
1 | package de.gnmyt.mcdash.panel.routes.worlds;
2 |
3 | import de.gnmyt.mcdash.api.handler.DefaultHandler;
4 | import de.gnmyt.mcdash.api.http.Request;
5 | import de.gnmyt.mcdash.api.http.ResponseController;
6 | import org.bukkit.Bukkit;
7 |
8 | public class WeatherRoute extends DefaultHandler {
9 |
10 | @Override
11 | public String path() {
12 | return "weather";
13 | }
14 |
15 | /**
16 | * Changes the weather of a specific world
17 | * @param request The request object from the HttpExchange
18 | * @param response The response controller from the HttpExchange
19 | * @throws Exception An exception that can be thrown
20 | */
21 | @Override
22 | public void patch(Request request, ResponseController response) throws Exception {
23 | if (!isStringInBody(request, response, "weather")) return;
24 | if (!isStringInBody(request, response, "world")) return;
25 |
26 | String weather = getStringFromBody(request, "weather");
27 | String world = getStringFromBody(request, "world");
28 |
29 | if (weather.equalsIgnoreCase("rain")) {
30 | runSync(() -> {
31 | Bukkit.getWorld(world).setStorm(true);
32 | Bukkit.getWorld(world).setThundering(false);
33 | });
34 | } else if (weather.equalsIgnoreCase("thunder")) {
35 | runSync(() -> {
36 | Bukkit.getWorld(world).setStorm(true);
37 | Bukkit.getWorld(world).setThundering(true);
38 | });
39 | } else if (weather.equalsIgnoreCase("clear")) {
40 | runSync(() -> {
41 | Bukkit.getWorld(world).setStorm(false);
42 | Bukkit.getWorld(world).setThundering(false);
43 | });
44 | } else {
45 | response.code(400).message("The weather must be 'rain', 'thunder' or 'clear'");
46 | }
47 |
48 | response.message("Successfully updated the weather of the world " + world);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/main/resources/plugin.yml:
--------------------------------------------------------------------------------
1 | name: MinecraftDashboard
2 | main: de.gnmyt.mcdash.MinecraftDashboard
3 | version: 1.1.7
4 | api-version: 1.13
5 | description: MCDash is a simple dashboard for your Minecraft server.
6 | author: GNMYT
7 | commands:
8 | panel:
9 | description: Changes the password of the current user
10 | aliases: [password, passwd]
11 | permission: mcdash.use
--------------------------------------------------------------------------------
/webui/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | dist
12 | dist-ssr
13 | *.local
14 |
15 | # Editor directories and files
16 | .vscode/*
17 | !.vscode/extensions.json
18 | .idea
19 | .DS_Store
20 | *.suo
21 | *.ntvs*
22 | *.njsproj
23 | *.sln
24 | *.sw?
25 |
--------------------------------------------------------------------------------
/webui/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | MCDash
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/webui/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "paths": {
5 | "@/*": ["./src/*"],
6 | "@components/*": ["./src/common/components/*"],
7 | "@contexts/*": ["./src/common/contexts/*"]
8 | }
9 | }
10 | }
--------------------------------------------------------------------------------
/webui/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "webui",
3 | "private": true,
4 | "version": "1.1.7",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vite build",
9 | "lint": "eslint src --ext js,jsx --report-unused-disable-directives --max-warnings 0",
10 | "preview": "vite preview"
11 | },
12 | "dependencies": {
13 | "@emotion/react": "^11.11.1",
14 | "@emotion/styled": "^11.11.0",
15 | "@fontsource/roboto": "^5.0.3",
16 | "@mui/icons-material": "^5.11.16",
17 | "@mui/material": "^5.13.5",
18 | "@mui/x-charts": "^6.0.0-alpha.7",
19 | "@mui/x-data-grid": "^6.8.0",
20 | "@uiw/codemirror-theme-dracula": "^4.21.13",
21 | "@uiw/react-codemirror": "^4.21.3",
22 | "buffer": "^6.0.3",
23 | "i18next": "^23.4.4",
24 | "i18next-browser-languagedetector": "^7.1.0",
25 | "i18next-http-backend": "^2.2.1",
26 | "react": "^18.2.0",
27 | "react-dom": "^18.2.0",
28 | "react-i18next": "^13.1.2",
29 | "react-router-dom": "^6.13.0",
30 | "xterm": "^5.2.1",
31 | "xterm-addon-fit": "^0.7.0"
32 | },
33 | "devDependencies": {
34 | "@types/react": "^18.0.37",
35 | "@types/react-dom": "^18.0.11",
36 | "@vitejs/plugin-react": "^4.0.0",
37 | "eslint": "^8.38.0",
38 | "eslint-plugin-react": "^7.32.2",
39 | "eslint-plugin-react-hooks": "^4.6.0",
40 | "eslint-plugin-react-refresh": "^0.3.4",
41 | "vite": "^4.3.9"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/webui/public/assets/img/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gnmyt/MCDash/dddc7ebcd3961cac3bde8aebab45c00252edfc20/webui/public/assets/img/favicon.ico
--------------------------------------------------------------------------------
/webui/public/assets/img/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gnmyt/MCDash/dddc7ebcd3961cac3bde8aebab45c00252edfc20/webui/public/assets/img/favicon.png
--------------------------------------------------------------------------------
/webui/src/App.jsx:
--------------------------------------------------------------------------------
1 | import {CssBaseline, ThemeProvider,} from "@mui/material";
2 | import darkTheme from "@/common/themes/dark.js";
3 | import lightTheme from "@/common/themes/light.js";
4 | import {createBrowserRouter, RouterProvider} from "react-router-dom";
5 | import Login from "@/states/Login";
6 | import Root from "@/states/Root";
7 | import {routes} from "@/common/routes/server.jsx";
8 | import {TokenProvider} from "@contexts/Token";
9 | import {PlayerProvider} from "@contexts/Players";
10 | import {useContext, useState} from "react";
11 | import {SettingsContext} from "@contexts/Settings";
12 | import i18n from "./i18n.js";
13 |
14 | const App = () => {
15 |
16 | const {theme} = useContext(SettingsContext);
17 | const [translationsLoaded, setTranslationsLoaded] = useState(false);
18 |
19 | const router = createBrowserRouter([
20 | {path: "/login", element: },
21 | {path: "/", element: , children: routes}
22 | ]);
23 |
24 | i18n.on("initialized", () => setTranslationsLoaded(true));
25 |
26 | if (!translationsLoaded) return <>>;
27 |
28 | return (
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | )
38 | }
39 |
40 | export default App;
--------------------------------------------------------------------------------
/webui/src/common/assets/images/end.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gnmyt/MCDash/dddc7ebcd3961cac3bde8aebab45c00252edfc20/webui/src/common/assets/images/end.webp
--------------------------------------------------------------------------------
/webui/src/common/assets/images/flags/de.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gnmyt/MCDash/dddc7ebcd3961cac3bde8aebab45c00252edfc20/webui/src/common/assets/images/flags/de.webp
--------------------------------------------------------------------------------
/webui/src/common/assets/images/flags/en.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gnmyt/MCDash/dddc7ebcd3961cac3bde8aebab45c00252edfc20/webui/src/common/assets/images/flags/en.webp
--------------------------------------------------------------------------------
/webui/src/common/assets/images/flags/es.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gnmyt/MCDash/dddc7ebcd3961cac3bde8aebab45c00252edfc20/webui/src/common/assets/images/flags/es.webp
--------------------------------------------------------------------------------
/webui/src/common/assets/images/flags/fr.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gnmyt/MCDash/dddc7ebcd3961cac3bde8aebab45c00252edfc20/webui/src/common/assets/images/flags/fr.webp
--------------------------------------------------------------------------------
/webui/src/common/assets/images/flags/ja.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gnmyt/MCDash/dddc7ebcd3961cac3bde8aebab45c00252edfc20/webui/src/common/assets/images/flags/ja.webp
--------------------------------------------------------------------------------
/webui/src/common/assets/images/flags/pl.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gnmyt/MCDash/dddc7ebcd3961cac3bde8aebab45c00252edfc20/webui/src/common/assets/images/flags/pl.webp
--------------------------------------------------------------------------------
/webui/src/common/assets/images/food.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gnmyt/MCDash/dddc7ebcd3961cac3bde8aebab45c00252edfc20/webui/src/common/assets/images/food.webp
--------------------------------------------------------------------------------
/webui/src/common/assets/images/health.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gnmyt/MCDash/dddc7ebcd3961cac3bde8aebab45c00252edfc20/webui/src/common/assets/images/health.webp
--------------------------------------------------------------------------------
/webui/src/common/assets/images/nether.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gnmyt/MCDash/dddc7ebcd3961cac3bde8aebab45c00252edfc20/webui/src/common/assets/images/nether.webp
--------------------------------------------------------------------------------
/webui/src/common/assets/images/overworld.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gnmyt/MCDash/dddc7ebcd3961cac3bde8aebab45c00252edfc20/webui/src/common/assets/images/overworld.webp
--------------------------------------------------------------------------------
/webui/src/common/assets/images/resource.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gnmyt/MCDash/dddc7ebcd3961cac3bde8aebab45c00252edfc20/webui/src/common/assets/images/resource.webp
--------------------------------------------------------------------------------
/webui/src/common/components/ActionConfirmDialog/ActionConfirmDialog.jsx:
--------------------------------------------------------------------------------
1 | import {
2 | Alert,
3 | Button,
4 | Dialog,
5 | DialogActions,
6 | DialogContent,
7 | DialogContentText,
8 | DialogTitle,
9 | Snackbar
10 | } from "@mui/material";
11 | import {useState} from "react";
12 | import {t} from "i18next";
13 |
14 | export const ActionConfirmDialog = ({open, setOpen, title, description, buttonText, onClick = () => {},
15 | successMessage}) => {
16 | const [snackbarOpen, setSnackbarOpen] = useState(false);
17 | const [actionFailed, setActionFailed] = useState(false);
18 |
19 | const confirm = async () => {
20 | setOpen(false);
21 |
22 | if (!(await onClick())) setActionFailed(true);
23 |
24 | setSnackbarOpen(true);
25 | }
26 |
27 | return (
28 | <>
29 | {successMessage !== "none" &&
30 | setSnackbarOpen(false)}
31 | anchorOrigin={{vertical: "bottom", horizontal: "right"}}>
32 | setSnackbarOpen(false)} severity={actionFailed ? "error" : "success"}
33 | sx={{width: '100%'}}>
34 | {actionFailed ? t("action.failed") : (successMessage || t("action.success"))}
35 |
36 | }
37 |
54 | >
55 | );
56 | }
--------------------------------------------------------------------------------
/webui/src/common/components/ActionConfirmDialog/index.js:
--------------------------------------------------------------------------------
1 | export {ActionConfirmDialog as default} from "./ActionConfirmDialog.jsx";
--------------------------------------------------------------------------------
/webui/src/common/contexts/Players/PlayerContext.jsx:
--------------------------------------------------------------------------------
1 | import {useState, useEffect, createContext} from "react";
2 | import {jsonRequest} from "@/common/utils/RequestUtil";
3 |
4 | export const PlayerContext = createContext({});
5 |
6 | export const PlayerProvider = (props) => {
7 |
8 | const [players, setPlayers] = useState([]);
9 |
10 | const updatePlayers = () => {
11 | jsonRequest("players/online").then(r => {
12 | if (!Array.isArray(r)) return;
13 | setPlayers(r)
14 | });
15 | }
16 |
17 | useEffect(() => {
18 | updatePlayers();
19 | const interval = setInterval(() => updatePlayers(), 5000);
20 | return () => clearInterval(interval);
21 | }, []);
22 |
23 | return (
24 |
25 | {props.children}
26 |
27 | );
28 | }
--------------------------------------------------------------------------------
/webui/src/common/contexts/Players/index.js:
--------------------------------------------------------------------------------
1 | export * from "./PlayerContext.jsx";
--------------------------------------------------------------------------------
/webui/src/common/contexts/Settings/SettingsContext.jsx:
--------------------------------------------------------------------------------
1 | import {createContext, useState} from "react";
2 | import i18n from "i18next";
3 |
4 | export const SettingsContext = createContext({});
5 |
6 | export const SettingsProvider = (props) => {
7 |
8 | const [theme, setTheme] = useState(localStorage.getItem("theme") || "dark");
9 | const [language, setLanguage] = useState(localStorage.getItem("language") || "en");
10 |
11 | const updateTheme = (theme) => {
12 | localStorage.setItem("theme", theme);
13 | setTheme(theme);
14 | }
15 |
16 | const updateLanguage = (language) => {
17 | localStorage.setItem("language", language);
18 | i18n.changeLanguage(language);
19 | setLanguage(language);
20 | }
21 |
22 | return (
23 |
24 | {props.children}
25 |
26 | )
27 | }
--------------------------------------------------------------------------------
/webui/src/common/contexts/Settings/index.js:
--------------------------------------------------------------------------------
1 | export * from "./SettingsContext.jsx";
--------------------------------------------------------------------------------
/webui/src/common/contexts/Token/TokenContext.jsx:
--------------------------------------------------------------------------------
1 | import {useState, createContext, useEffect} from "react";
2 | import {request} from "@/common/utils/RequestUtil.js";
3 | import {Alert, Snackbar} from "@mui/material";
4 | import {t} from "i18next";
5 |
6 | export const TokenContext = createContext({});
7 |
8 | export const TokenProvider = (props) => {
9 | const [tokenValid, setTokenValid] = useState(null);
10 | const [serverOnline, setServerOnline] = useState(null);
11 |
12 | const checkToken = () => request("ping").then((r) => {
13 | if (!r.ok && !(r.status === 400 || r.status === 401)) throw new Error("Server unavailable");
14 | setTokenValid(r.status === 200);
15 | setServerOnline(true);
16 | return r.status === 200;
17 | }).catch(() => {
18 | setServerOnline(false);
19 | });
20 |
21 | useEffect(() => {
22 | checkToken();
23 | const interval = setInterval(() => checkToken(), 10000);
24 | return () => clearInterval(interval);
25 | }, []);
26 |
27 | return (
28 |
29 |
30 |
31 | {t("info.server_unavailable")}
32 |
33 |
34 | {props.children}
35 |
36 | )
37 | }
--------------------------------------------------------------------------------
/webui/src/common/contexts/Token/index.js:
--------------------------------------------------------------------------------
1 | export * from './TokenContext.jsx';
--------------------------------------------------------------------------------
/webui/src/common/themes/dark.js:
--------------------------------------------------------------------------------
1 | import { createTheme } from '@mui/material/styles';
2 |
3 | const theme = createTheme({
4 | palette: {
5 | mode: 'dark',
6 | primary: {
7 | main: '#556cd6',
8 | },
9 | background: {
10 | default: '#27232F',
11 | darker: "#1A1722",
12 | card: '#2b2937',
13 | }
14 | },
15 | components: {
16 | MuiDrawer: {
17 | styleOverrides: {
18 | paper: {
19 | backgroundColor: '#1A1722'
20 | }
21 | }
22 | },
23 | MuiAppBar: {
24 | styleOverrides: {
25 | root: {
26 | backgroundColor: '#1A1722'
27 | }
28 | }
29 | },
30 | MuiMenu: {
31 | styleOverrides: {
32 | paper: {
33 | backgroundColor: '#1A1722'
34 | }
35 | }
36 | },
37 | MuiListItemButton: {
38 | styleOverrides: {
39 | root: {
40 | borderRadius: 15,
41 | margin: 4,
42 | marginLeft: 7,
43 | marginRight: 7
44 | }
45 | }
46 | },
47 | MuiCssBaseline: {
48 | styleOverrides: {
49 | body: {
50 | "&::-webkit-scrollbar, & *::-webkit-scrollbar": {
51 | width: 8,
52 | },
53 | "&::-webkit-scrollbar-thumb, & *::-webkit-scrollbar-thumb": {
54 | borderRadius: 8,
55 | minHeight: 24,
56 | backgroundColor: "#34333e",
57 | },
58 | "&::-webkit-scrollbar-thumb:hover, & *::-webkit-scrollbar-thumb:hover": {
59 | backgroundColor: "#3B3A4A"
60 | }
61 | },
62 | },
63 | },
64 | },
65 | shape: {
66 | borderRadius: 8,
67 | }
68 | });
69 |
70 | export default theme;
--------------------------------------------------------------------------------
/webui/src/common/themes/light.js:
--------------------------------------------------------------------------------
1 | import { createTheme } from '@mui/material/styles';
2 |
3 | const theme = createTheme({
4 | palette: {
5 | mode: 'light',
6 | primary: {
7 | main: '#556cd6',
8 | },
9 | background: {
10 | card: '#f1f1f1',
11 | default: '#F4F6F8',
12 | darker: "#e5e5e5",
13 | }
14 | },
15 | components: {
16 | MuiAppBar: {
17 | styleOverrides: {
18 | root: {
19 | backgroundColor: '#ffffff'
20 | }
21 | }
22 | },
23 | MuiTypography: {
24 | styleOverrides: {
25 | root: {
26 | color: '#000000'
27 | }
28 | }
29 | },
30 | MuiListItemButton: {
31 | styleOverrides: {
32 | root: {
33 | borderRadius: 15,
34 | margin: 4,
35 | marginLeft: 7,
36 | marginRight: 7
37 | }
38 | }
39 | },
40 | MuiCssBaseline: {
41 | styleOverrides: {
42 | body: {
43 | "&::-webkit-scrollbar, & *::-webkit-scrollbar": {
44 | width: 8,
45 | },
46 | "&::-webkit-scrollbar-thumb, & *::-webkit-scrollbar-thumb": {
47 | borderRadius: 8,
48 | minHeight: 24,
49 |
50 | backgroundColor: "#adadb0",
51 | },
52 | "&::-webkit-scrollbar-thumb:hover, & *::-webkit-scrollbar-thumb:hover": {
53 | backgroundColor: "#3B3A4A"
54 | }
55 | },
56 | },
57 | },
58 | },
59 | shape: {
60 | borderRadius: 8,
61 | }
62 | });
63 |
64 | export default theme;
--------------------------------------------------------------------------------
/webui/src/common/utils/StringUtil.js:
--------------------------------------------------------------------------------
1 | export const capitalizeFirst = (string) => {
2 | return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase();
3 | }
--------------------------------------------------------------------------------
/webui/src/i18n.js:
--------------------------------------------------------------------------------
1 | import i18n from "i18next";
2 | import {initReactI18next} from "react-i18next";
3 | import LanguageDetector from 'i18next-browser-languagedetector';
4 | import HttpApi from 'i18next-http-backend';
5 |
6 | if (localStorage.getItem('language') === null)
7 | localStorage.setItem('language', navigator.language.split('-')[0]);
8 |
9 | export const languages = {
10 | de: "Deutsch",
11 | en: "English",
12 | es: "Español",
13 | fr: "Français",
14 | ja: "日本語",
15 | pl: "Polski"
16 | }
17 |
18 | i18n.use(initReactI18next).use(LanguageDetector).use(HttpApi).init({
19 | supportedLngs: Object.keys(languages),
20 | fallbackLng: 'en',
21 | backend: {
22 | loadPath: '/assets/locales/{{lng}}.json'
23 | },
24 | detection: {
25 | order: ['localStorage'],
26 | lookupLocalStorage: 'language'
27 | }
28 | });
29 |
30 | export default i18n;
--------------------------------------------------------------------------------
/webui/src/main.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom/client";
3 | import App from "./App.jsx";
4 | import "@fontsource/roboto/300.css";
5 | import "@fontsource/roboto/400.css";
6 | import "@fontsource/roboto/500.css";
7 | import "@fontsource/roboto/700.css";
8 | import {SettingsProvider} from "@contexts/Settings";
9 |
10 |
11 | ReactDOM.createRoot(document.getElementById('root')).render(
12 |
13 |
14 |
15 |
16 | ,
17 | )
18 |
--------------------------------------------------------------------------------
/webui/src/states/Login/index.js:
--------------------------------------------------------------------------------
1 | export {Login as default} from "./Login.jsx";
--------------------------------------------------------------------------------
/webui/src/states/Root/Root.jsx:
--------------------------------------------------------------------------------
1 | import {Navigate, Outlet} from "react-router-dom";
2 | import {TokenContext} from "@contexts/Token";
3 | import {useContext, useState} from "react";
4 | import {Box, Toolbar} from "@mui/material";
5 | import Sidebar from "@/states/Root/components/Sidebar";
6 | import Header from "@/states/Root/components/Header";
7 | import ServerDown from "@/states/Root/pages/ServerDown";
8 |
9 | export const Root = () => {
10 |
11 | const [mobileOpen, setMobileOpen] = useState(false);
12 | const {tokenValid, serverOnline} = useContext(TokenContext);
13 |
14 | return (
15 | <>
16 | {tokenValid === false && }
17 |
18 | {tokenValid === null && serverOnline === false && }
19 |
20 | {tokenValid &&
21 | setMobileOpen(current => !current)} />
22 | setMobileOpen(current => !current)} />
23 |
24 |
25 |
26 |
27 | }
28 | >
29 | )
30 | }
--------------------------------------------------------------------------------
/webui/src/states/Root/components/Header/components/AccountMenu/components/ChangeLanguageDialog/index.js:
--------------------------------------------------------------------------------
1 | export {ChangeLanguageDialog as default} from "./ChangeLanguageDialog.jsx";
--------------------------------------------------------------------------------
/webui/src/states/Root/components/Header/components/AccountMenu/index.js:
--------------------------------------------------------------------------------
1 | export {AccountMenu as default} from "./AccountMenu.jsx";
--------------------------------------------------------------------------------
/webui/src/states/Root/components/Header/components/UpdateDialog/UpdateDialog.jsx:
--------------------------------------------------------------------------------
1 | import {Alert, Button, Dialog, DialogActions, DialogContent, DialogTitle, Snackbar, Typography} from "@mui/material";
2 | import {postRequest} from "@/common/utils/RequestUtil.js";
3 | import {useState} from "react";
4 | import {t} from "i18next";
5 |
6 | export const UpdateDialog = ({open, setOpen, latest, current, setVersionInfo}) => {
7 |
8 | const [snackbarOpen, setSnackbarOpen] = useState(false);
9 |
10 | const reload = (reload = false) => {
11 | setOpen(false);
12 | setSnackbarOpen(true);
13 | postRequest("update", {reloadAfterUpdate: reload}).then(() => {
14 | if (reload) setTimeout(() => window.location.reload(), 5000);
15 | setTimeout(() => setSnackbarOpen(false), 5000);
16 | setVersionInfo({available: false, latest, current});
17 | });
18 | }
19 |
20 | return (
21 | <>
22 |
23 |
24 | {t("update.updating", {latest})}
25 |
26 |
27 |
40 | >
41 | );
42 | }
--------------------------------------------------------------------------------
/webui/src/states/Root/components/Header/components/UpdateDialog/index.js:
--------------------------------------------------------------------------------
1 | export {UpdateDialog as default} from "./UpdateDialog.jsx";
--------------------------------------------------------------------------------
/webui/src/states/Root/components/Header/index.js:
--------------------------------------------------------------------------------
1 | export {Header as default} from "./Header.jsx";
--------------------------------------------------------------------------------
/webui/src/states/Root/components/Sidebar/Sidebar.jsx:
--------------------------------------------------------------------------------
1 | import {
2 | Divider, Drawer,
3 | List,
4 | ListItem,
5 | ListItemButton,
6 | ListItemIcon,
7 | ListItemText,
8 | Stack,
9 | Toolbar,
10 | Typography
11 | } from "@mui/material";
12 | import {sidebar} from "@/common/routes/server.jsx";
13 | import {useLocation, useNavigate} from "react-router-dom";
14 |
15 | const drawerWidth = 240;
16 |
17 | export const Sidebar = ({mobileOpen, toggleOpen, window: containerWindow}) => {
18 | const location = useLocation();
19 | const navigate = useNavigate();
20 |
21 | const container = containerWindow !== undefined ? () => containerWindow().document.body : undefined;
22 |
23 | const isSelected = (path) => {
24 | if (path === "/") return location.pathname === "/";
25 |
26 | return location.pathname.startsWith(path);
27 | }
28 |
29 | const drawer = (
30 | <>
31 |
32 |
33 |
34 | MCDash
35 |
36 |
37 |
38 |
39 |
40 |
41 | {sidebar.map((route) => (
42 |
43 | navigate(route.path.replace("/*", ""))}>
44 | {route.icon}
45 |
46 |
47 |
48 | ))}
49 |
50 |
51 | >
52 | );
53 |
54 | return (
55 | <>
56 |
59 | {drawer}
60 |
61 |
63 | {drawer}
64 |
65 | >
66 | )
67 | }
--------------------------------------------------------------------------------
/webui/src/states/Root/components/Sidebar/index.js:
--------------------------------------------------------------------------------
1 | export {Sidebar as default} from "./Sidebar.jsx";
--------------------------------------------------------------------------------
/webui/src/states/Root/index.js:
--------------------------------------------------------------------------------
1 | export {Root as default} from "./Root.jsx";
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Backups/components/BackupCreationDialog/index.js:
--------------------------------------------------------------------------------
1 | export {BackupCreationDialog as default} from "./BackupCreationDialog.jsx";
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Backups/components/BackupItem/components/RestoreDialog/RestoreDialog.jsx:
--------------------------------------------------------------------------------
1 | import {
2 | Alert,
3 | Box, Button,
4 | Dialog,
5 | DialogActions,
6 | DialogContent,
7 | DialogContentText,
8 | DialogTitle, Snackbar,
9 | } from "@mui/material";
10 | import React, {useState} from "react";
11 | import {postRequest} from "@/common/utils/RequestUtil.js";
12 | import {t} from "i18next";
13 |
14 | export const RestoreDialog = ({open, setOpen, id}) => {
15 |
16 | const [actionFinished, setActionFinished] = useState(false);
17 |
18 | const executeAction = (haltAfterRestore = false) => {
19 | setOpen(false);
20 | postRequest("backups/restore", {backup_id: id, halt: haltAfterRestore})
21 | .then(() => setActionFinished(true))
22 | .catch(() => setActionFinished(true));
23 | }
24 |
25 | return (
26 | <>
27 | setActionFinished(false)}
28 | anchorOrigin={{vertical: "bottom", horizontal: "right"}}>
29 | setActionFinished(false)} severity="success" sx={{width: '100%'}}>
30 | {t("backup.restored")}
31 |
32 |
33 |
34 |
49 | >
50 | );
51 | }
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Backups/components/BackupItem/components/RestoreDialog/index.js:
--------------------------------------------------------------------------------
1 | export {RestoreDialog as default} from "./RestoreDialog.jsx";
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Backups/components/BackupItem/index.js:
--------------------------------------------------------------------------------
1 | export {BackupItem as default} from "./BackupItem.jsx";
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Backups/components/BackupItem/mappings.jsx:
--------------------------------------------------------------------------------
1 | import {Check, Description, Extension, Public, Settings} from "@mui/icons-material";
2 | import {Tooltip} from "@mui/material";
3 | import React from "react";
4 | import {t} from "i18next";
5 |
6 | export default () => ({
7 | "0": ,
8 | "1": ,
9 | "2": ,
10 | "3": ,
11 | "4":
12 | });
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Backups/contexts/Backups/BackupContext.jsx:
--------------------------------------------------------------------------------
1 | import {createContext, useEffect, useState} from "react";
2 | import {jsonRequest} from "@/common/utils/RequestUtil.js";
3 |
4 | export const BackupContext = createContext({});
5 |
6 | export const BackupProvider = (props) => {
7 | const [backups, setBackups] = useState([]);
8 |
9 | const updateBackups = () => jsonRequest("backups/").then((r) => setBackups(r.reverse()));
10 |
11 | useEffect(() => {
12 | updateBackups();
13 | const interval = setInterval(() => updateBackups(), 5000);
14 | return () => clearInterval(interval);
15 | }, []);
16 |
17 | return (
18 |
19 | {props.children}
20 |
21 | )
22 | }
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Backups/contexts/Backups/index.js:
--------------------------------------------------------------------------------
1 | export * from "./BackupContext.jsx";
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Backups/index.js:
--------------------------------------------------------------------------------
1 | export {Backups as default} from "./Backups.jsx";
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Configuration/components/ConfigurationItem/index.js:
--------------------------------------------------------------------------------
1 | export {ConfigurationItem as default} from "./ConfigurationItem.jsx";
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Configuration/contexts/Properties/PropertiesContext.jsx:
--------------------------------------------------------------------------------
1 | import {useState, useEffect, createContext} from "react";
2 | import {jsonRequest} from "@/common/utils/RequestUtil";
3 |
4 | export const PropertiesContext = createContext({});
5 |
6 | export const PropertiesProvider = (props) => {
7 |
8 | const [properties, setProperties] = useState([]);
9 |
10 | const updateProperties = () => {
11 | jsonRequest("manage/properties").then((r) => setProperties(r.sort((a, b) => a.name.localeCompare(b.name))));
12 | }
13 |
14 | useEffect(() => {
15 | updateProperties();
16 | const interval = setInterval(() => updateProperties(), 5000);
17 | return () => clearInterval(interval);
18 | }, []);
19 |
20 | return (
21 |
22 | {props.children}
23 |
24 | );
25 | }
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Configuration/contexts/Properties/index.js:
--------------------------------------------------------------------------------
1 | export * from "./PropertiesContext.jsx";
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Configuration/contexts/SSHStatus/index.js:
--------------------------------------------------------------------------------
1 | export * from "./SSHStatusContext.jsx";
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Configuration/index.js:
--------------------------------------------------------------------------------
1 | export {Configuration as default} from "./Configuration.jsx";
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Console/custom.css:
--------------------------------------------------------------------------------
1 | .xterm-cursor {
2 | visibility: hidden;
3 | }
4 |
5 | .xterm-screen {
6 | border-radius: 1rem;
7 | }
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Console/index.js:
--------------------------------------------------------------------------------
1 | export {Console as default} from "./Console";
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Files/components/FileDropdown/FileDropdown.jsx:
--------------------------------------------------------------------------------
1 | import {Menu, MenuItem} from "@mui/material";
2 | import {Delete, Download} from "@mui/icons-material";
3 | import {deleteRequest, downloadRequest} from "@/common/utils/RequestUtil.js";
4 | import {t} from "i18next";
5 |
6 | export const FileDropdown = ({contextMenu, setContextMenu, directory, setFiles, setSnackbar}) => {
7 |
8 | const handleClose = () => {
9 | setContextMenu(null);
10 | }
11 |
12 | const deleteFile = (file) => {
13 | deleteRequest("filebrowser/file", {path: "." + directory + file.name}).then(() => {
14 | setFiles(files => files.filter((f) => f.name !== file.name));
15 | setSnackbar(t("files.file_deleted"));
16 | });
17 | }
18 |
19 | const deleteFolder = (file) => {
20 | deleteRequest("filebrowser/folder", {path: "." + directory + file.name}).then(() => {
21 | setFiles(files => files.filter((f) => f.name !== file.name));
22 | setSnackbar(t("files.folder_deleted"));
23 | });
24 | }
25 |
26 | const handleDelete = () => {
27 | if (!contextMenu.file) return;
28 |
29 | if (contextMenu.file.is_folder)
30 | deleteFolder(contextMenu.file);
31 | else deleteFile(contextMenu.file);
32 |
33 | handleClose();
34 | }
35 |
36 | const downloadFile = () => {
37 | if (!contextMenu.file || contextMenu?.file?.is_folder) return;
38 |
39 | downloadRequest("filebrowser/file?path=." + directory + contextMenu.file.name);
40 |
41 | handleClose();
42 | }
43 |
44 | return (
45 |
57 | );
58 | }
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Files/components/FileDropdown/index.js:
--------------------------------------------------------------------------------
1 | export {FileDropdown as default} from './FileDropdown';
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Files/components/FileEditor/FileEditor.jsx:
--------------------------------------------------------------------------------
1 | import CodeMirror from "@uiw/react-codemirror";
2 | import {dracula} from "@uiw/codemirror-theme-dracula";
3 | import {Box, Fab} from "@mui/material";
4 | import {useContext, useEffect, useState} from "react";
5 | import {patchRequest, request} from "@/common/utils/RequestUtil.js";
6 | import {Save} from "@mui/icons-material";
7 | import {t} from "i18next";
8 | import {SettingsContext} from "@contexts/Settings";
9 |
10 | export const FileEditor = ({directory, currentFile, setSnackbar}) => {
11 | const {theme} = useContext(SettingsContext);
12 |
13 | const [fileContent, setFileContent] = useState("");
14 | const [fileContentChanged, setFileContentChanged] = useState(false);
15 |
16 | useEffect(() => {
17 | if (currentFile === null) return setFileContent(null);
18 |
19 | request("filebrowser/file?path=." + directory + currentFile.name)
20 | .then(async (data) => setFileContent(await data.text()));
21 | }, [currentFile]);
22 |
23 | useEffect(() => {
24 | return () => setFileContent(null);
25 | }, []);
26 |
27 | const saveFile = () => {
28 | patchRequest("filebrowser/file", {path: "." + directory + currentFile.name, content: fileContent}).then(() => {
29 | setFileContentChanged(false);
30 | setSnackbar(t("files.file_saved"));
31 | });
32 | }
33 |
34 | const updateContent = (value) => {
35 | setFileContentChanged(true);
36 | setFileContent(value);
37 | }
38 |
39 | return (
40 |
41 | {fileContentChanged && }
43 |
44 |
46 |
47 |
48 | )
49 | }
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Files/components/FileEditor/index.js:
--------------------------------------------------------------------------------
1 | export {FileEditor as default} from './FileEditor';
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Files/components/FileHeader/components/NewFolderDialog/NewFolderDialog.jsx:
--------------------------------------------------------------------------------
1 | import {Button, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, TextField} from "@mui/material";
2 | import {useEffect, useState} from "react";
3 | import {putRequest} from "@/common/utils/RequestUtil.js";
4 | import {t} from "i18next";
5 |
6 | export const NewFolderDialog = ({open, setOpen, directory, updateFiles, setSnackbar}) => {
7 |
8 | const [folderName, setFolderName] = useState("");
9 |
10 | useEffect(() => {
11 | setFolderName("");
12 | }, [open]);
13 |
14 | const create = (event) => {
15 | if (event) event.preventDefault();
16 | if (!folderName) return;
17 | setOpen(false);
18 |
19 | putRequest("filebrowser/folder", {path: "." + directory + folderName}).then(() => {
20 | updateFiles();
21 | setSnackbar(t("files.folder_created"));
22 | });
23 | }
24 |
25 | return (
26 |
44 | );
45 | }
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Files/components/FileHeader/components/NewFolderDialog/index.js:
--------------------------------------------------------------------------------
1 | export {NewFolderDialog as default} from "./NewFolderDialog.jsx";
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Files/components/FileHeader/index.js:
--------------------------------------------------------------------------------
1 | export {FileHeader as default} from './FileHeader';
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Files/components/FileView/FileView.jsx:
--------------------------------------------------------------------------------
1 | import {Box, Button, Stack, Typography} from "@mui/material";
2 | import {Folder, InsertDriveFile} from "@mui/icons-material";
3 | import {convertSize} from "./utils/FileUtil.js";
4 | import {t} from "i18next";
5 |
6 | export const FileView = ({files, changeDirectory, click, handleContextMenu}) => {
7 | return (
8 |
9 | {files.length === 0 &&
10 | }
11 | {files.map((file) => (
12 | click(file, event)}
14 | backgroundColor={"background.darker"} borderRadius={2.5} style={{cursor: "pointer"}}
15 | alignItems="center"
16 | onContextMenu={(e) => handleContextMenu(e, file)}>
17 | {file.is_folder && }
18 |
19 | {!file.is_folder && }
20 |
21 | {file.name}
22 |
23 |
24 | {!file.is_folder && {convertSize(file.size)}}
25 | {new Date(file.last_modified).toLocaleString()}
26 |
27 |
28 | ))}
29 |
30 | );
31 | }
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Files/components/FileView/index.js:
--------------------------------------------------------------------------------
1 | export {FileView as default} from "./FileView.jsx";
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Files/components/FileView/utils/FileUtil.js:
--------------------------------------------------------------------------------
1 | export const convertSize = (size) => {
2 | if (size < 1024) return size + " B";
3 | else if (size < 1024 * 1024) return (size / 1024).toFixed(2) + " KB";
4 | else if (size < 1024 * 1024 * 1024) return (size / (1024 * 1024)).toFixed(2) + " MB";
5 | else return (size / (1024 * 1024 * 1024)).toFixed(2) + " GB";
6 | }
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Files/index.js:
--------------------------------------------------------------------------------
1 | export {Files as default} from "./Files.jsx";
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Overview/components/ChartBox/ChartBox.jsx:
--------------------------------------------------------------------------------
1 | import {Box, Stack, Typography} from "@mui/material";
2 | import {LineChart} from "@mui/x-charts";
3 |
4 | export const ChartBox = ({title, value, formatter = (v) => v, icon, color}) => {
5 | const iconStyle = {borderRadius: 0.5, width: 25, height: 25,
6 | justifyContent: "center", alignItems: "center", "& svg": {width: 22, height: 22}};
7 |
8 | return (
9 |
11 |
12 |
13 |
14 | {icon}
15 |
16 | {title}
17 |
18 | {formatter(value[value.length - 1])}
19 |
20 | {value.length >= 1 && i), valueFormatter: (n) => new Date(Date.now()
24 | - (value.length - n) * 1000).toLocaleTimeString()
25 | }]}
26 | margin={{top: 10, bottom: 0, left: 0, right: 0}}
27 | height={40}
28 | sx={{'& .MuiMarkElement-root': {display: 'none'}, '& .MuiChartsAxis-root': {display: 'none'}}}
29 | />}
30 |
31 | )
32 | }
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Overview/components/ChartBox/index.js:
--------------------------------------------------------------------------------
1 | export {ChartBox as default} from "./ChartBox.jsx";
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Overview/components/OverviewArea/index.js:
--------------------------------------------------------------------------------
1 | export {OverviewArea as default} from "./OverviewArea.jsx";
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Overview/components/StatisticBox/StatisticBox.jsx:
--------------------------------------------------------------------------------
1 | import {Box, Stack, Typography} from "@mui/material";
2 |
3 | export const StatisticBox = ({title, value, icon, color}) => {
4 | const iconStyle = {borderRadius: 1.5, width: 50, height: 50,
5 | justifyContent: "center", alignItems: "center", "& svg": {width: 30, height: 30},
6 | "& img": {width: 50, height: 50, borderRadius: 1.5}};
7 |
8 | return (
9 |
12 |
13 |
14 | {icon}
15 |
16 |
17 | {title}
18 | {value}
19 |
20 |
21 |
22 | )
23 | }
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Overview/components/StatisticBox/index.js:
--------------------------------------------------------------------------------
1 | export {StatisticBox as default} from "./StatisticBox.jsx";
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Overview/components/WelcomeTip/WelcomeTip.jsx:
--------------------------------------------------------------------------------
1 | import {Box, Button, IconButton, Stack, Tooltip, Typography} from "@mui/material";
2 | import {t} from "i18next";
3 | import {Book, Close, Favorite} from "@mui/icons-material";
4 |
5 | export const WelcomeTip = ({handleWelcomeClose}) => {
6 | return (
7 |
9 |
10 |
11 |
12 | {t("overview.tip.welcome")}
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | {t("overview.tip.text")}
{t("overview.tip.subtext")}
21 |
22 |
23 |
27 |
31 |
32 |
33 | );
34 | }
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Overview/components/WelcomeTip/index.js:
--------------------------------------------------------------------------------
1 | export {WelcomeTip as default} from "./WelcomeTip.jsx";
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Overview/contexts/StatsContext/StatsContext.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect, createContext } from "react";
2 | import { jsonRequest } from "@/common/utils/RequestUtil";
3 |
4 | export const StatsContext = createContext({});
5 |
6 | export const StatsProvider = (props) => {
7 | const [stats, setStats] = useState([]);
8 |
9 | const updateStats = () => {
10 | jsonRequest("stats/").then((r) => {
11 | setStats((prevStats) => {
12 | const newStats = [...prevStats, {date: new Date(), ...r}];
13 | if (newStats.length > 20)
14 | newStats.shift();
15 | return newStats;
16 | });
17 | });
18 | };
19 |
20 | useEffect(() => {
21 | updateStats();
22 | const interval = setInterval(() => updateStats(), 1000);
23 | return () => clearInterval(interval);
24 | }, []);
25 |
26 | return (
27 |
28 | {props.children}
29 |
30 | );
31 | };
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Overview/contexts/StatsContext/index.js:
--------------------------------------------------------------------------------
1 | export * from "./StatsContext";
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Overview/index.js:
--------------------------------------------------------------------------------
1 | export {Overview as default} from "./Overview.jsx";
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Players/components/BanListTable/BanListTable.jsx:
--------------------------------------------------------------------------------
1 | import {useContext} from "react";
2 | import {BanListContext} from "@/states/Root/pages/Players/contexts/BanList";
3 | import {DataGrid} from "@mui/x-data-grid";
4 | import columns from "./columns.jsx";
5 | import {Stack} from "@mui/material";
6 | import {t} from "i18next";
7 |
8 | export const BanListTable = ({setSelectedBannedPlayers}) => {
9 | const {bannedPlayers} = useContext(BanListContext);
10 |
11 | return (
12 | <>
13 | ({id: player.uuid, ...player}))}
15 | columns={columns()}
16 | initialState={{pagination: {paginationModel: {page: 0, pageSize: 10}}}}
17 | pageSizeOptions={[10, 25, 50]}
18 | checkboxSelection
19 | disableColumnFilter={true}
20 | disableColumnMenu={true}
21 | onRowSelectionModelChange={(newSelection) => {
22 | setSelectedBannedPlayers(newSelection)
23 | }}
24 | sx={{display: 'grid', gridTemplateRows: 'auto 1f auto'}}
25 | autoHeight={true}
26 | slots={{
27 | noRowsOverlay: () =>
28 | {t("players.none.banned")}
29 |
30 | }}
31 | slotProps={{pagination: {labelRowsPerPage: t("players.per_page")}}}
32 | />
33 | >
34 | );
35 | }
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Players/components/BanListTable/columns.jsx:
--------------------------------------------------------------------------------
1 | import {Typography} from "@mui/material";
2 | import {t} from "i18next";
3 |
4 | const columns = () => [
5 | {
6 | field: 'name', headerName: t("players.username"), minWidth: 150, flex: 1, renderCell: (params) => {
7 | return (
8 |
9 |

11 |
{params.row.name}
12 |
13 | )
14 | }
15 | },
16 | {field: 'uuid', headerName: t("players.id"), flex: 1, minWidth: 200},
17 | {field: 'reason', headerName: t("players.reason"), flex: 1, minWidth: 150},
18 | {
19 | field: 'last_seen', headerName: t("players.last_seen"), flex: 1, minWidth: 100, renderCell: (params) =>
20 | ({new Date(params.row.last_seen).toLocaleString()})
21 | }
22 | ];
23 |
24 | export default columns;
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Players/components/BanListTable/index.js:
--------------------------------------------------------------------------------
1 | export {BanListTable as default} from "./BanListTable.jsx";
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Players/components/PlayerActionDialog/index.js:
--------------------------------------------------------------------------------
1 | export {PlayerActionDialog as default} from "./PlayerActionDialog.jsx";
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Players/components/PlayerTable/components/PlayerTeleportDialog/index.js:
--------------------------------------------------------------------------------
1 | export {PlayerTeleportDialog as default} from "./PlayerTeleportDialog.jsx";
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Players/components/PlayerTable/index.js:
--------------------------------------------------------------------------------
1 | export {PlayerTable as default} from "./PlayerTable.jsx";
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Players/components/PlayerTable/utils/formatter.jsx:
--------------------------------------------------------------------------------
1 | import {Typography} from "@mui/material";
2 | import {capitalizeFirst} from "@/common/utils/StringUtil.js";
3 | import {t} from "i18next";
4 |
5 | export const formatWorld = (world) => {
6 | if (world === "world") return {t("worlds.overworld")};
7 | if (world === "world_nether") return {t("worlds.nether")};
8 | if (world === "world_the_end") return {t("worlds.end")};
9 | return {capitalizeFirst(world)};
10 | }
11 |
12 | export const formatTime = (ticks) => {
13 | const totalSeconds = Math.floor(ticks / 20);
14 | const hours = Math.floor(totalSeconds / 3600);
15 | const minutes = Math.floor((totalSeconds % 3600) / 60);
16 | const seconds = totalSeconds % 60;
17 |
18 | return (hours < 10 ? "0" + hours : hours) + ":" + (minutes < 10 ? "0" + minutes : minutes) + ":" + (seconds < 10 ? "0" + seconds : seconds);
19 | }
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Players/components/WhiteListAddDialog/WhitelistAddDialog.jsx:
--------------------------------------------------------------------------------
1 | import {
2 | Box,
3 | Button,
4 | Dialog,
5 | DialogActions,
6 | DialogContent,
7 | DialogTitle,
8 | TextField
9 | } from "@mui/material";
10 | import {useContext, useEffect, useState} from "react";
11 | import {WhiteListContext} from "@/states/Root/pages/Players/contexts/WhiteList";
12 | import {putRequest} from "@/common/utils/RequestUtil.js";
13 | import {t} from "i18next";
14 |
15 | export const WhitelistAddDialog = ({open, setOpen}) => {
16 | const {updatePlayers} = useContext(WhiteListContext);
17 |
18 | const [playerName, setPlayerName] = useState("");
19 |
20 | useEffect(() => {
21 | if (!open) setPlayerName("");
22 | }, [open]);
23 |
24 | const executeAction = async (event) => {
25 | if (event) event.preventDefault();
26 |
27 | putRequest(`players/whitelist`, {username: playerName})
28 | .then(() => updatePlayers());
29 |
30 | setOpen(false);
31 | }
32 |
33 | return (
34 |
47 | );
48 | }
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Players/components/WhiteListAddDialog/index.js:
--------------------------------------------------------------------------------
1 | export {WhitelistAddDialog as default} from "./WhitelistAddDialog";
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Players/components/WhiteListTable/WhiteListTable.jsx:
--------------------------------------------------------------------------------
1 | import {useContext} from "react";
2 | import {WhiteListContext} from "@/states/Root/pages/Players/contexts/WhiteList";
3 | import {DataGrid} from "@mui/x-data-grid";
4 | import columns from "./columns.jsx";
5 | import {Stack} from "@mui/material";
6 | import {t} from "i18next";
7 |
8 | export const WhiteListTable = ({setSelectedWhitelistedPlayers}) => {
9 | const {whitelistedPlayers} = useContext(WhiteListContext);
10 |
11 | return (
12 | <>
13 | ({id: player?.uuid, ...player}))}
15 | columns={columns()}
16 | initialState={{pagination: {paginationModel: {page: 0, pageSize: 10}}}}
17 | pageSizeOptions={[10, 25, 50]}
18 | checkboxSelection
19 | disableColumnFilter={true}
20 | disableColumnMenu={true}
21 | onRowSelectionModelChange={(newSelection) => {
22 | setSelectedWhitelistedPlayers(newSelection);
23 | }}
24 | sx={{display: 'grid', gridTemplateRows: 'auto 1f auto'}}
25 | autoHeight={true}
26 | slots={{
27 | noRowsOverlay: () =>
28 | {t("players.none.whitelisted")}
29 |
30 | }}
31 | slotProps={{pagination: {labelRowsPerPage: t("players.per_page")}}}
32 | />
33 | >
34 | );
35 | }
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Players/components/WhiteListTable/columns.jsx:
--------------------------------------------------------------------------------
1 | import {Typography} from "@mui/material";
2 | import {t} from "i18next";
3 |
4 | const columns = () => [
5 | {
6 | field: 'name', headerName: t("players.username"), minWidth: 150, flex: 1, renderCell: (params) => {
7 | return (
8 |
9 |

11 |
{params.row.name}
12 |
13 | )
14 | }
15 | },
16 | {field: 'uuid', headerName: t("players.id"), flex: 1, minWidth: 200},
17 | {
18 | field: 'last_seen', headerName: t("players.last_seen"), flex: 1, minWidth: 100, renderCell: (params) =>
19 | ({new Date(params.row.last_seen).getDate() === 1 ? "-" : new Date(params.row.last_seen).toLocaleString()})
20 | }
21 | ];
22 |
23 | export default columns;
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Players/components/WhiteListTable/index.js:
--------------------------------------------------------------------------------
1 | export {WhiteListTable as default} from "./WhiteListTable.jsx";
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Players/contexts/BanList/BanListContext.jsx:
--------------------------------------------------------------------------------
1 | import {useState, useEffect, createContext} from "react";
2 | import {jsonRequest} from "@/common/utils/RequestUtil";
3 |
4 | export const BanListContext = createContext({});
5 |
6 | export const BanListProvider = (props) => {
7 |
8 | const [bannedPlayers, setBannedPlayers] = useState([]);
9 |
10 | const updatePlayers = () => {
11 | jsonRequest("players/banlist").then(r => {
12 | if (!Array.isArray(r)) return;
13 | setBannedPlayers(r)
14 | });
15 | }
16 |
17 | useEffect(() => {
18 | updatePlayers();
19 | const interval = setInterval(() => updatePlayers(), 5000);
20 | return () => clearInterval(interval);
21 | }, []);
22 |
23 | return (
24 |
25 | {props.children}
26 |
27 | );
28 | }
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Players/contexts/BanList/index.js:
--------------------------------------------------------------------------------
1 | export * from "./BanListContext.jsx";
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Players/contexts/WhiteList/WhiteListContext.jsx:
--------------------------------------------------------------------------------
1 | import {useState, useEffect, createContext} from "react";
2 | import {jsonRequest, patchRequest} from "@/common/utils/RequestUtil";
3 |
4 | export const WhiteListContext = createContext({});
5 |
6 | export const WhiteListProvider = (props) => {
7 |
8 | const [whitelistedPlayers, setWhitelistedPlayers] = useState([]);
9 | const [whitelistActive, setWhitelistActive] = useState(false);
10 |
11 | const updatePlayers = () => {
12 | jsonRequest("players/whitelist").then(r => {
13 | if (!Array.isArray(r)) return;
14 | setWhitelistedPlayers(r)
15 | });
16 |
17 | jsonRequest("action/whitelist").then(r => setWhitelistActive(r.status));
18 | }
19 |
20 | useEffect(() => {
21 | updatePlayers();
22 | const interval = setInterval(() => updatePlayers(), 5000);
23 | return () => clearInterval(interval);
24 | }, []);
25 |
26 | const switchWhitelist = () => {
27 | patchRequest("action/whitelist", {status: !whitelistActive})
28 | .then(() => updatePlayers());
29 | }
30 |
31 | return (
32 |
33 | {props.children}
34 |
35 | );
36 | }
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Players/contexts/WhiteList/index.js:
--------------------------------------------------------------------------------
1 | export * from "./WhiteListContext.jsx";
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Players/index.js:
--------------------------------------------------------------------------------
1 | export {Players as default} from "./Players.jsx";
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Plugins/Plugins.jsx:
--------------------------------------------------------------------------------
1 | import {Box, Button, Chip, Stack, TextField, Typography} from "@mui/material";
2 | import React, {useContext, useState} from "react";
3 | import {PluginsContext} from "@/states/Root/pages/Plugins/contexts/Plugins";
4 | import {PluginItem} from "@/states/Root/pages/Plugins/components/PluginItem/PluginItem.jsx";
5 | import {Store} from "@mui/icons-material";
6 | import PluginStore from "@/states/Root/pages/Plugins/components/PluginStore";
7 | import {t} from "i18next";
8 |
9 | export const Plugins = () => {
10 | const [storeOpen, setStoreOpen] = useState(false);
11 | const {plugins} = useContext(PluginsContext);
12 |
13 | const [search, setSearch] = useState("");
14 | const [currentSearch, setCurrentSearch] = useState("");
15 |
16 | return (
17 | <>
18 |
19 | {t("nav.plugins")}
21 |
22 |
23 | {storeOpen && setSearch(e.target.value)} color="secondary"
25 | onKeyUp={(e) => e.key === "Enter" && setCurrentSearch(search)}
26 | onBlur={() => setCurrentSearch(search)} />}
27 |
29 |
30 |
31 |
32 | {!storeOpen &&
33 | {plugins.map((plugin) => )}
34 | }
35 |
36 | {storeOpen && setStoreOpen(false)} />}
37 | >
38 | )
39 | }
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Plugins/components/PluginItem/index.js:
--------------------------------------------------------------------------------
1 | export {PluginItem as default} from "./PluginItem.jsx";
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Plugins/components/PluginStore/PluginStore.jsx:
--------------------------------------------------------------------------------
1 | import {useContext, useEffect, useState} from "react";
2 | import {jsonRequest} from "@/common/utils/RequestUtil.js";
3 | import StoreItem from "@/states/Root/pages/Plugins/components/PluginStore/components/StoreItem";
4 | import {Stack, Typography} from "@mui/material";
5 | import {PluginsContext} from "@/states/Root/pages/Plugins/contexts/Plugins";
6 | import {t} from "i18next";
7 |
8 | export const PluginStore = ({search, closeStore}) => {
9 | const [plugins, setPlugins] = useState([]);
10 | const [pluginOffset, setPluginOffset] = useState(1);
11 |
12 | const {plugins: currentPlugins} = useContext(PluginsContext);
13 |
14 | const loadPlugins = (reset = false) => {
15 | jsonRequest("store/?page=" + pluginOffset + (search ? "&query=" + search : "")).then((data) => setPlugins(current => {
16 | if (reset) return data;
17 | const ids = current.map((p) => p.id);
18 | const newPlugins = data.filter((p) => !ids.includes(p.id));
19 |
20 | return [...current, ...newPlugins];
21 | }));
22 | }
23 |
24 | useEffect(() => {
25 | loadPlugins();
26 |
27 | const loadWithOffset = () => {
28 | if (window.innerHeight + window.scrollY >= document.body.offsetHeight)
29 | setPluginOffset(current => current + 1);
30 | }
31 |
32 | window.addEventListener("scroll", loadWithOffset);
33 |
34 | return () => window.removeEventListener("scroll", loadWithOffset);
35 | }, []);
36 |
37 | useEffect(() => {
38 | loadPlugins();
39 | }, [pluginOffset]);
40 |
41 | useEffect(() => {
42 | setPluginOffset(1);
43 | loadPlugins(true);
44 | }, [search]);
45 |
46 | return (
47 | <>
48 |
49 | {plugins.map((plugin) => p.path
51 | ?.startsWith("Managed-" + plugin.id + ".jar"))} />)}
52 |
53 | {plugins.length === 0 && {t("plugins.no_plugins")}}
54 |
55 | >
56 | )
57 |
58 | }
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Plugins/components/PluginStore/components/StoreItem/index.js:
--------------------------------------------------------------------------------
1 | export {StoreItem as default} from "./StoreItem.jsx";
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Plugins/components/PluginStore/components/StoreItem/utils.js:
--------------------------------------------------------------------------------
1 | export const prettyDownloadCount = (count) => {
2 | if (count < 1000) return count;
3 | if (count < 1000000) return (count / 1000).toFixed(1) + "K";
4 | return (count / 1000000).toFixed(1) + "M";
5 | }
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Plugins/components/PluginStore/index.js:
--------------------------------------------------------------------------------
1 | export {PluginStore as default} from "./PluginStore.jsx";
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Plugins/contexts/Plugins/PluginsContext.jsx:
--------------------------------------------------------------------------------
1 | import {useState, useEffect, createContext} from "react";
2 | import {jsonRequest} from "@/common/utils/RequestUtil";
3 |
4 | export const PluginsContext = createContext({});
5 |
6 | export const PluginsProvider = (props) => {
7 |
8 | const [plugins, setPlugins] = useState([]);
9 |
10 | const updatePlugins = () => {
11 | jsonRequest("plugin/list").then((r) => setPlugins(r));
12 | }
13 |
14 | useEffect(() => {
15 | updatePlugins();
16 | const interval = setInterval(() => updatePlugins(), 5000);
17 | return () => clearInterval(interval);
18 | }, []);
19 |
20 | return (
21 |
22 | {props.children}
23 |
24 | );
25 | }
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Plugins/contexts/Plugins/index.js:
--------------------------------------------------------------------------------
1 | export * from "./PluginsContext.jsx";
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Plugins/index.js:
--------------------------------------------------------------------------------
1 | export {Plugins as default} from "./Plugins";
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Scheduler/Scheduler.jsx:
--------------------------------------------------------------------------------
1 | import {Button, Stack, Typography} from "@mui/material";
2 | import {t} from "i18next";
3 | import {Add} from "@mui/icons-material";
4 | import React, {useContext, useState} from "react";
5 | import {SchedulesContext} from "@/states/Root/pages/Scheduler/contexts/Schedules";
6 | import Schedule from "@/states/Root/pages/Scheduler/components/Schedule";
7 | import CreateScheduleDialog from "@/states/Root/pages/Scheduler/components/CreateScheduleDialog";
8 |
9 | export const Scheduler = () => {
10 |
11 | const {schedules} = useContext(SchedulesContext);
12 | const [dialogOpen, setDialogOpen] = useState(false);
13 |
14 | return (
15 |
16 |
17 |
18 | {t("nav.schedules")}
19 |
21 |
22 |
23 |
24 | {schedules.length === 0 && {t("schedules.none_created")}}
25 | {schedules.length !== 0 &&
26 | {schedules.slice(0, Math.ceil(schedules.length / 2)).map((schedule) => (
27 | ))}
28 | }
29 | {schedules.length !== 0 &&
30 | {schedules.slice(Math.ceil(schedules.length / 2)).map((schedule) => (
31 | ))}
32 | }
33 |
34 |
35 | )
36 | }
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Scheduler/components/Action/components/EditActionDialog/EditActionDialog.jsx:
--------------------------------------------------------------------------------
1 | import {Button, Dialog, DialogActions, DialogContent, DialogTitle, Stack, TextField} from "@mui/material";
2 | import {SchedulesContext} from "@/states/Root/pages/Scheduler/contexts/Schedules";
3 | import {useContext, useState} from "react";
4 | import {patchRequest} from "@/common/utils/RequestUtil.js";
5 | import {t} from "i18next";
6 |
7 | export const EditActionDialog = ({open, setOpen, actionType, payload, index, actions, name}) => {
8 |
9 | const {updateSchedules} = useContext(SchedulesContext);
10 |
11 | const [newPayload, setPayload] = useState(payload);
12 |
13 | const editAction = async (event) => {
14 | event.preventDefault();
15 |
16 | const newActions = [...actions];
17 | newActions[index] = {type: actionType, payload: newPayload};
18 |
19 | await patchRequest("schedules/actions", {name, actions: JSON.stringify(newActions)});
20 |
21 | updateSchedules();
22 | setOpen(false);
23 | }
24 |
25 | return (
26 |
40 | );
41 | }
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Scheduler/components/Action/components/EditActionDialog/index.js:
--------------------------------------------------------------------------------
1 | export {EditActionDialog as default} from "./EditActionDialog.jsx";
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Scheduler/components/Action/index.js:
--------------------------------------------------------------------------------
1 | export {Action as default} from "./Action.jsx";
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Scheduler/components/Action/mappings.jsx:
--------------------------------------------------------------------------------
1 | import {Campaign, Group, PowerSettingsNew, Replay, Save, Terminal} from "@mui/icons-material";
2 | import {t} from "i18next";
3 |
4 | export default () => ({
5 | "1": {
6 | icon: ,
7 | name: t("schedules.types.command"),
8 | },
9 | "2": {
10 | icon: ,
11 | name: t("schedules.types.broadcast"),
12 | },
13 | "3": {
14 | icon: ,
15 | name: t("schedules.types.reload"),
16 | },
17 | "4": {
18 | icon: ,
19 | name: t("schedules.types.stop"),
20 | },
21 | "5": {
22 | icon: ,
23 | name: t("schedules.types.backup"),
24 | },
25 | "6": {
26 | icon: ,
27 | name: t("schedules.types.kick"),
28 | }
29 | });
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Scheduler/components/CreateActionDialog/index.js:
--------------------------------------------------------------------------------
1 | export {CreateActionDialog as default} from "./CreateActionDialog.jsx";
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Scheduler/components/CreateScheduleDialog/index.js:
--------------------------------------------------------------------------------
1 | export {CreateScheduleDialog as default} from "./CreateScheduleDialog.jsx";
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Scheduler/components/CreateScheduleDialog/utils.js:
--------------------------------------------------------------------------------
1 | import {t} from "i18next";
2 |
3 | export const getDays = () => [t("schedules.weekly.monday"), t("schedules.weekly.tuesday"),
4 | t("schedules.weekly.wednesday"), t("schedules.weekly.thursday"), t("schedules.weekly.friday"),
5 | t("schedules.weekly.saturday"), t("schedules.weekly.sunday")];
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Scheduler/components/Schedule/index.js:
--------------------------------------------------------------------------------
1 | export {Schedule as default} from "./Schedule.jsx";
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Scheduler/components/Schedule/utils.js:
--------------------------------------------------------------------------------
1 | import {t} from "i18next";
2 | import {getDays} from "@/states/Root/pages/Scheduler/components/CreateScheduleDialog/utils.js";
3 |
4 | export const convertFrequency = (frequency, time) => {
5 | if (frequency === "MONTHLY")
6 | return t("schedules.schedule.monthly", {day: parseInt(time)});
7 |
8 | if (frequency === "WEEKLY")
9 | return t("schedules.schedule.weekly", {days: getDays()[parseInt(time)-1]});
10 |
11 | if (frequency === "DAILY")
12 | return t("schedules.schedule.daily", {time: time.replace(/(.{2})/, "$1:")});
13 |
14 | if (frequency === "HOURLY")
15 | return t("schedules.schedule.hourly", {minutes: parseInt(time)});
16 | }
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Scheduler/contexts/Schedules/SchedulesContext.jsx:
--------------------------------------------------------------------------------
1 | import {createContext, useEffect, useState} from "react";
2 | import {jsonRequest} from "@/common/utils/RequestUtil.js";
3 |
4 | export const SchedulesContext = createContext({});
5 |
6 | export const ScheduleProvider = ({children}) => {
7 |
8 | const [schedules, setSchedules] = useState([]);
9 |
10 | const updateSchedules = () => {
11 | jsonRequest("schedules/").then((res) => setSchedules(res));
12 | }
13 |
14 | useEffect(() => {
15 | updateSchedules();
16 |
17 | const interval = setInterval(() => updateSchedules(), 5000);
18 | return () => clearInterval(interval);
19 | }, []);
20 |
21 | return (
22 |
23 | {children}
24 |
25 | )
26 | }
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Scheduler/contexts/Schedules/index.js:
--------------------------------------------------------------------------------
1 | export * from "./SchedulesContext.jsx";
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Scheduler/index.js:
--------------------------------------------------------------------------------
1 | export {Scheduler as default} from "./Scheduler.jsx";
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/ServerDown/ServerDown.jsx:
--------------------------------------------------------------------------------
1 | import {Stack, Typography, useMediaQuery, useTheme} from "@mui/material";
2 | import {t} from "i18next";
3 |
4 | export const ServerDown = () => {
5 | const theme = useTheme();
6 | const isMobile = useMediaQuery(theme.breakpoints.down("lg"));
7 |
8 | return (
9 |
10 | {!isMobile &&
}
11 |
12 |
13 | {t("info.down.title")}
14 |
15 |
16 | {t("info.down.description")}
17 |
18 |
19 |
20 | );
21 | }
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/ServerDown/index.js:
--------------------------------------------------------------------------------
1 | export {ServerDown as default} from "./ServerDown.jsx";
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Worlds/Worlds.jsx:
--------------------------------------------------------------------------------
1 | import {Box, Button, Stack, Typography} from "@mui/material";
2 | import React, {useContext, useState} from "react";
3 | import {WorldsContext} from "@/states/Root/pages/Worlds/contexts/Worlds";
4 |
5 | import {Add} from "@mui/icons-material";
6 | import CreateWorldDialog from "@/states/Root/pages/Worlds/components/CreateWorldDialog";
7 | import WorldItem from "@/states/Root/pages/Worlds/components/WorldItem";
8 | import {t} from "i18next";
9 |
10 | export const Worlds = () => {
11 |
12 | const [createWorldDialogOpen, setCreateWorldDialogOpen] = useState(false);
13 | const {worlds} = useContext(WorldsContext);
14 |
15 | return (
16 | <>
17 |
18 |
19 | {t("nav.worlds")}
20 |
21 |
22 |
24 |
25 |
26 |
27 |
28 | {worlds.map((world) => ())}
29 |
30 |
31 | >
32 | );
33 | }
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Worlds/components/CreateWorldDialog/CreateWorldDialog.jsx:
--------------------------------------------------------------------------------
1 | import {
2 | Box,
3 | Button,
4 | Dialog,
5 | DialogActions,
6 | DialogContent,
7 | DialogTitle,
8 | MenuItem,
9 | Select,
10 | Stack,
11 | TextField
12 | } from "@mui/material";
13 | import {useContext, useEffect, useState} from "react";
14 | import {WorldsContext} from "@/states/Root/pages/Worlds/contexts/Worlds";
15 | import {putRequest} from "@/common/utils/RequestUtil.js";
16 | import {t} from "i18next";
17 |
18 | export const CreateWorldDialog = ({open, setOpen}) => {
19 | const {updateWorlds} = useContext(WorldsContext);
20 | const [name, setName] = useState("");
21 | const [environment, setEnvironment] = useState("normal");
22 |
23 | const create = (event) => {
24 | event.preventDefault();
25 |
26 | putRequest("worlds/", {name, environment}).then(() => {
27 | updateWorlds();
28 | setOpen(false);
29 | });
30 | }
31 |
32 | useEffect(() => {
33 | setName("");
34 | setEnvironment("normal");
35 | }, [open]);
36 |
37 | return (
38 |
60 | )
61 | }
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Worlds/components/CreateWorldDialog/index.js:
--------------------------------------------------------------------------------
1 | export {CreateWorldDialog as default} from "./CreateWorldDialog.jsx";
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Worlds/components/WorldItem/components/DifficultyDialog/index.js:
--------------------------------------------------------------------------------
1 | export {DifficultyDialog as default} from "./DifficultyDialog.jsx";
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Worlds/components/WorldItem/components/TimeDialog/TimeDialog.jsx:
--------------------------------------------------------------------------------
1 | import {Alert, Box, Button, Dialog, DialogActions, DialogContent, DialogTitle, Slider, Snackbar,} from "@mui/material";
2 | import {patchRequest} from "@/common/utils/RequestUtil.js";
3 | import {WorldsContext} from "@/states/Root/pages/Worlds/contexts/Worlds";
4 | import {useContext, useState} from "react";
5 | import {mapTime, timeMarks} from "@/states/Root/pages/Worlds/components/WorldItem/utils.js";
6 | import {t} from "i18next";
7 |
8 | export const TimeDialog = ({open, setOpen, time, name}) => {
9 |
10 | const {updateWorlds} = useContext(WorldsContext);
11 | const [snackbarOpen, setSnackbarOpen] = useState(false);
12 |
13 | const [value, setValue] = useState(time);
14 |
15 | const changeTime = (time) => {
16 | patchRequest("worlds/time", {world: name, time}).then(() => {
17 | updateWorlds();
18 | setOpen(null);
19 | setSnackbarOpen(true);
20 | });
21 | }
22 |
23 | return (
24 | <>
25 | setSnackbarOpen(false)}
26 | anchorOrigin={{vertical: "bottom", horizontal: "right"}}>
27 | setSnackbarOpen(false)} severity={"success"} sx={{width: '100%'}}>
28 | {t("worlds.time.changed")}
29 |
30 |
31 |
45 | >
46 | );
47 | }
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Worlds/components/WorldItem/components/TimeDialog/index.js:
--------------------------------------------------------------------------------
1 | export {TimeDialog as default} from "./TimeDialog.jsx";
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Worlds/components/WorldItem/components/WeatherDialog/WeatherDialog.jsx:
--------------------------------------------------------------------------------
1 | import {Alert, Box, Dialog, DialogContent, DialogTitle, IconButton, Snackbar, Stack,} from "@mui/material";
2 | import {Thunderstorm, WaterDrop, WbSunny} from "@mui/icons-material";
3 | import {patchRequest} from "@/common/utils/RequestUtil.js";
4 | import {WorldsContext} from "@/states/Root/pages/Worlds/contexts/Worlds";
5 | import {useContext, useState} from "react";
6 | import {t} from "i18next";
7 |
8 | export const WeatherDialog = ({open, setOpen, weather, name}) => {
9 |
10 | const {updateWorlds} = useContext(WorldsContext);
11 | const [snackbarOpen, setSnackbarOpen] = useState(false);
12 |
13 | const changeWeather = (weather) => {
14 | patchRequest("worlds/weather", {world: name, weather}).then(() => {
15 | updateWorlds();
16 | setOpen(null);
17 | setSnackbarOpen(true);
18 | });
19 | }
20 |
21 | return (
22 | <>
23 | setSnackbarOpen(false)}
24 | anchorOrigin={{vertical: "bottom", horizontal: "right"}}>
25 | setSnackbarOpen(false)} severity={"success"} sx={{width: '100%'}}>
26 | {t("worlds.weather.changed")}
27 |
28 |
29 |
44 | >
45 | );
46 | }
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Worlds/components/WorldItem/components/WeatherDialog/index.js:
--------------------------------------------------------------------------------
1 | export {WeatherDialog as default} from "./WeatherDialog.jsx";
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Worlds/components/WorldItem/components/WorldHeader/WorldHeader.jsx:
--------------------------------------------------------------------------------
1 | import {Box, Typography} from "@mui/material";
2 | import {mapName} from "@/states/Root/pages/Worlds/components/WorldItem/utils.js";
3 | import NetherImage from "@/common/assets/images/nether.webp";
4 | import EndImage from "@/common/assets/images/end.webp";
5 | import OverworldImage from "@/common/assets/images/overworld.webp";
6 |
7 | export const WorldHeader = ({environment, name}) => {
8 | return (
9 |
10 | {mapName(name)}
11 |
12 |
14 |
15 | );
16 | }
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Worlds/components/WorldItem/components/WorldHeader/index.js:
--------------------------------------------------------------------------------
1 | export {WorldHeader as default} from "./WorldHeader.jsx";
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Worlds/components/WorldItem/components/WorldInfo/WorldInfo.jsx:
--------------------------------------------------------------------------------
1 | import {Stack, Typography} from "@mui/material";
2 | import {Cloud, Gavel, Group, Timelapse} from "@mui/icons-material";
3 | import {mapTime} from "@/states/Root/pages/Worlds/components/WorldItem/utils.js";
4 | import {capitalizeFirst} from "@/common/utils/StringUtil.js";
5 | import {t} from "i18next";
6 |
7 | export const WorldInfo = ({players, time, weather, difficulty}) => {
8 | return (
9 |
10 |
11 |
12 | {t("worlds.info.players")}: {players}
14 |
15 |
16 |
17 | {t("worlds.info.time")}: {mapTime(time)}
19 |
20 |
21 |
22 |
23 | {t("worlds.info.weather")}: {capitalizeFirst(weather)}
25 |
26 |
27 |
28 |
29 | {t("worlds.info.difficulty")}: {capitalizeFirst(difficulty)}
31 |
32 |
33 | )
34 | }
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Worlds/components/WorldItem/components/WorldInfo/index.js:
--------------------------------------------------------------------------------
1 | export {WorldInfo as default} from "./WorldInfo.jsx";
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Worlds/components/WorldItem/index.js:
--------------------------------------------------------------------------------
1 | export {WorldItem as default} from "./WorldItem.jsx";
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Worlds/components/WorldItem/utils.js:
--------------------------------------------------------------------------------
1 | import {t} from "i18next";
2 |
3 | export const mapName = (worldName) => {
4 | if (worldName === "world") return "Overworld";
5 | if (worldName === "world_nether") return "Nether";
6 | if (worldName === "world_the_end") return "The End";
7 | return worldName;
8 | }
9 |
10 | export const mapTime = (minecraftTime) => {
11 | const time = minecraftTime % 24000;
12 |
13 | if (time < 1000) return t("time.sunrise");
14 | if (time < 12000) return t("time.day");
15 | if (time < 13000) return t("time.sunset");
16 | if (time < 23000) return t("time.night");
17 | return "Sunrise";
18 | }
19 |
20 | export const timeMarks = () => [
21 | { value: 1000, label: t("time.day") },
22 | { value: 13000, label: t("time.night") },
23 | { value: 23000, label: t("time.sunrise") }
24 | ]
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Worlds/contexts/Worlds/WorldsContext.jsx:
--------------------------------------------------------------------------------
1 | import {createContext, useEffect, useState} from "react";
2 | import {jsonRequest} from "@/common/utils/RequestUtil.js";
3 |
4 | export const WorldsContext = createContext({});
5 |
6 | export const WorldsProvider = (props) => {
7 |
8 | const [worlds, setWorlds] = useState([]);
9 |
10 | const updateWorlds = () => {
11 | jsonRequest("worlds/")
12 | .then((data) => setWorlds(data));
13 | }
14 |
15 | useEffect(() => {
16 | updateWorlds();
17 | const interval = setInterval(updateWorlds, 5000);
18 | return () => clearInterval(interval);
19 | }, []);
20 |
21 | return (
22 |
23 | {props.children}
24 |
25 | );
26 | }
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Worlds/contexts/Worlds/index.js:
--------------------------------------------------------------------------------
1 | export * from "./WorldsContext.jsx";
--------------------------------------------------------------------------------
/webui/src/states/Root/pages/Worlds/index.js:
--------------------------------------------------------------------------------
1 | export {Worlds as default} from "./Worlds.jsx";
--------------------------------------------------------------------------------
/webui/vite.config.js:
--------------------------------------------------------------------------------
1 | import {defineConfig} from "vite";
2 | import path from "path";
3 | import react from "@vitejs/plugin-react";
4 |
5 | export default defineConfig({
6 | plugins: [react()],
7 | server: {
8 | proxy: {
9 | "/api": "http://localhost:7867"
10 | }
11 | },
12 | resolve: {
13 | alias: {
14 | '@contexts': path.resolve(__dirname, './src/common/contexts'),
15 | '@components': path.resolve(__dirname, './src/common/components'),
16 | '@': path.resolve(__dirname, './src'),
17 | }
18 | }
19 | })
20 |
--------------------------------------------------------------------------------