├── .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 | [![SpigotMC](https://img.shields.io/badge/Spigot-_?style=for-the-badge&color=EF8E21)](https://www.spigotmc.org/resources/110687/) 11 | [![Modrinth](https://img.shields.io/badge/Modrinth-_?style=for-the-badge&color=26292F&logo=modrinth)](https://modrinth.com/plugin/mcdash) 12 | [![GitHub](https://img.shields.io/badge/GitHub-_?style=for-the-badge&color=181717&logo=github)](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 | ![Configuration](/img/screenshots/backups.png) 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 | ![Configuration](/img/screenshots/configuration.png) 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 | ![Console](/img/screenshots/console.png) 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 | ![File Manager](/img/screenshots/file_manager.png) 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 | ![Configuration](/img/screenshots/overview.png) 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 | ![Players](/img/screenshots/players.png) 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 | ![Plugins](/img/screenshots/plugins.png) 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 | ![Create Server](/img/screenshots/wrapper/create.png) 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 | ![Dashboard](/img/screenshots/wrapper/home.png) 39 | 40 | ![Menu Open](/img/screenshots/wrapper/menu.png) 41 | 42 | ![Dashboard Open](/img/screenshots/wrapper/open.png) -------------------------------------------------------------------------------- /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 | {title} 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 |
14 |
15 |

{siteConfig.title}

16 |

{siteConfig.tagline}

17 |
18 | 19 | 📘 Read the documentation 20 | 21 | 22 | 🔮 Wrapper 23 | 24 |
25 |
26 |
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 | setOpen(false)} aria-labelledby="alert-dialog-title" 38 | aria-describedby="alert-dialog-description"> 39 | 40 | {title || t("action.confirm")} 41 | 42 | 43 | 44 | {description || t("action.sure")} 45 | 46 | 47 | 48 | 49 | 52 | 53 | 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 | setOpen(false)}> 28 | {t("update.available")} {current} ➤ {latest} 30 | 31 | 32 | {t("update.info")} 33 | 34 | 35 | 36 | 37 | 38 | 39 | 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 | MCDash 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 | setOpen(false)}> 35 | 36 | {t("backup.restoring")} 37 | 38 | 39 | {t("backup.restore_text")} 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 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 | 48 | {!contextMenu?.file?.is_folder && 49 | 50 | {t("files.download")} 51 | } 52 | 53 | 54 | {t("files.delete")} 55 | 56 | 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 | setOpen(false)} aria-labelledby="alert-dialog-title" 27 | aria-describedby="alert-dialog-description" component="form" onSubmit={create}> 28 | 29 | {t("files.create_folder.title")} 30 | 31 | 32 | 33 | {t("files.create_folder.text")} 34 | 35 | 36 | setFolderName(e.target.value)}/> 38 | 39 | 40 | 41 | 42 | 43 | 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 | MCDash 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 | {params.row.name} 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 | setOpen(false)}> 35 | 36 | {t("players.whitelist_title")} 37 | 38 | setPlayerName(e.target.value)}/> 40 | 41 | 42 | 43 | 44 | 45 | 46 | 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 | {params.row.name} 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 | setOpen(false)} component="form" onSubmit={editAction}> 27 | {t("schedules.action.edit")} 28 | 29 | 30 | {(actionType === 1 || actionType === 2 || actionType === 6) && 31 | setPayload(e.target.value)} fullWidth 32 | label={t("schedules.action." + (actionType === 1 ? "command" : actionType === 2 ? "message" : "kick"))}/>} 33 | 34 | 35 | 36 | 37 | 38 | 39 | 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 && MCDash logo} 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 | setOpen(false)}> 39 | 40 | {t("worlds.create_title")} 41 | 42 | 43 | setName(e.target.value)}/> 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 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 | setOpen(null)}> 32 | 33 | {t("worlds.time.title")} 34 | 35 | setValue(v)} min={0} max={24000} 36 | sx={{mt: 2}} 37 | step={100} marks={timeMarks()}/> 38 | 39 | 40 | 41 | 42 | 43 | 44 | 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 | setOpen(null)}> 30 | 31 | {t("worlds.weather.title")} 32 | 33 | 34 | changeWeather("clear")}> 36 | changeWeather("rain")}> 38 | changeWeather("thunder")}> 40 | 41 | 42 | 43 | 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 | --------------------------------------------------------------------------------