mirror of
https://github.com/tcsenpai/rconuicraft.git
synced 2025-06-02 17:30:09 +00:00
first commit
This commit is contained in:
commit
3c99a8e60a
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
old/
|
||||
.env
|
||||
package-lock.json
|
||||
node_modules/
|
||||
|
7
backup.sh
Executable file
7
backup.sh
Executable file
@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
rm -rf old/*
|
||||
cp -r public old/
|
||||
cp server.js old/
|
||||
|
||||
cat /dev/null > server.js
|
||||
cat /dev/null > public/index.html
|
15
env.example
Normal file
15
env.example
Normal file
@ -0,0 +1,15 @@
|
||||
# Server Configuration
|
||||
PORT=3000
|
||||
|
||||
# Admin Credentials
|
||||
ADMIN_USERNAME=admin
|
||||
ADMIN_PASSWORD=setyourpassword
|
||||
|
||||
# RCON Configuration
|
||||
RCON_HOST=localhost
|
||||
RCON_PORT=25575
|
||||
RCON_PASSWORD=yourrconpassword
|
||||
|
||||
# Paths
|
||||
MODS_PATH=/your/server/mods
|
||||
SERVER_PATH=/your/server
|
20
package.json
Normal file
20
package.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "mine",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"start": "node server.js"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"dotenv": "^16.4.5",
|
||||
"express": "^4.21.1",
|
||||
"express-basic-auth": "^1.2.1",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"rcon-client": "^4.2.5"
|
||||
}
|
||||
}
|
305
public/index.html
Normal file
305
public/index.html
Normal file
@ -0,0 +1,305 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Minecraft Server Control Panel</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.header {
|
||||
background-color: #2c3e50;
|
||||
color: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.status-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.status-item {
|
||||
background-color: #fff;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
flex: 1;
|
||||
margin: 0 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.console {
|
||||
background-color: #2c3e50;
|
||||
color: #fff;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
height: 300px;
|
||||
overflow-y: auto;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.console-line {
|
||||
margin: 5px 0;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.command-input {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
input[type="text"] {
|
||||
flex: 1;
|
||||
padding: 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: #3498db;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 20px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #2980b9;
|
||||
}
|
||||
|
||||
.quick-commands {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.status-online {
|
||||
color: #2ecc71;
|
||||
}
|
||||
|
||||
.status-offline {
|
||||
color: #e74c3c;
|
||||
}
|
||||
|
||||
.mods-list {
|
||||
background-color: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.mod-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 10px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.mod-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
#loginError {
|
||||
background: #e74c3c;
|
||||
color: white;
|
||||
padding: 10px;
|
||||
margin: 10px;
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="loginError">Authentication failed. Please reload the page and try again.</div>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>Minecraft Server Control Panel</h1>
|
||||
</div>
|
||||
|
||||
<div class="status-container">
|
||||
<div class="status-item">
|
||||
<h3>Server Status</h3>
|
||||
<span id="serverStatus" class="status-offline">Offline</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<h3>Players</h3>
|
||||
<span id="playerCount">0/20</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<h3>TPS</h3>
|
||||
<span id="tps">20</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<h3>Uptime</h3>
|
||||
<span id="uptime">0:00:00</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="console" id="console"></div>
|
||||
|
||||
<div class="command-input">
|
||||
<input type="text" id="commandInput" placeholder="Enter command..." onkeydown="handleKeyDown(event)">
|
||||
<button onclick="sendCommand()">Send</button>
|
||||
</div>
|
||||
|
||||
<div class="quick-commands">
|
||||
<button onclick="sendCommand('list')">List Players</button>
|
||||
<button onclick="sendCommand('tps')">Check TPS</button>
|
||||
<button onclick="sendCommand('time query day')">Check Time</button>
|
||||
<button onclick="sendCommand('weather query')">Check Weather</button>
|
||||
</div>
|
||||
|
||||
<div class="mods-list">
|
||||
<h2>Installed Mods</h2>
|
||||
<div id="modsList"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let commandHistory = [];
|
||||
let historyIndex = -1;
|
||||
|
||||
function appendToConsole(command, response) {
|
||||
const console = document.getElementById('console');
|
||||
const timestamp = new Date().toLocaleTimeString();
|
||||
|
||||
if (command) {
|
||||
console.innerHTML += `<div class="console-line">[${timestamp}] > ${command}</div>`;
|
||||
}
|
||||
if (response) {
|
||||
console.innerHTML += `<div class="console-line">[${timestamp}] ${response}</div>`;
|
||||
}
|
||||
|
||||
console.scrollTop = console.scrollHeight;
|
||||
}
|
||||
|
||||
async function sendCommand(cmd) {
|
||||
const commandInput = document.getElementById('commandInput');
|
||||
const command = cmd || commandInput.value;
|
||||
|
||||
if (!command) return;
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/command', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ command }),
|
||||
});
|
||||
|
||||
if (response.status === 401) {
|
||||
document.getElementById('loginError').style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
appendToConsole(command, data.response);
|
||||
if (!cmd) {
|
||||
addToHistory(command);
|
||||
commandInput.value = '';
|
||||
}
|
||||
} else {
|
||||
appendToConsole(command, `Error: ${data.error}`);
|
||||
}
|
||||
} catch (error) {
|
||||
appendToConsole(command, `Error: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
function addToHistory(command) {
|
||||
commandHistory.unshift(command);
|
||||
if (commandHistory.length > 50) {
|
||||
commandHistory.pop();
|
||||
}
|
||||
historyIndex = -1;
|
||||
}
|
||||
|
||||
function handleKeyDown(event) {
|
||||
const commandInput = document.getElementById('commandInput');
|
||||
|
||||
if (event.key === 'Enter') {
|
||||
sendCommand();
|
||||
} else if (event.key === 'ArrowUp') {
|
||||
event.preventDefault();
|
||||
if (historyIndex < commandHistory.length - 1) {
|
||||
historyIndex++;
|
||||
commandInput.value = commandHistory[historyIndex];
|
||||
}
|
||||
} else if (event.key === 'ArrowDown') {
|
||||
event.preventDefault();
|
||||
if (historyIndex > -1) {
|
||||
historyIndex--;
|
||||
commandInput.value = historyIndex === -1 ? '' : commandHistory[historyIndex];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function updateServerStatus() {
|
||||
try {
|
||||
const response = await fetch('/api/stats');
|
||||
|
||||
if (response.status === 401) {
|
||||
document.getElementById('loginError').style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
const stats = await response.json();
|
||||
|
||||
document.getElementById('playerCount').textContent = `${stats.players}/${stats.maxPlayers}`;
|
||||
document.getElementById('tps').textContent = stats.tps.toFixed(1);
|
||||
document.getElementById('serverStatus').className = 'status-online';
|
||||
document.getElementById('serverStatus').textContent = 'Online';
|
||||
|
||||
const hours = Math.floor(stats.uptime / 3600);
|
||||
const minutes = Math.floor((stats.uptime % 3600) / 60);
|
||||
const seconds = stats.uptime % 60;
|
||||
document.getElementById('uptime').textContent =
|
||||
`${hours}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
|
||||
|
||||
const modsList = document.getElementById('modsList');
|
||||
modsList.innerHTML = '';
|
||||
stats.mods.forEach(mod => {
|
||||
const modDiv = document.createElement('div');
|
||||
modDiv.className = 'mod-item';
|
||||
modDiv.innerHTML = `
|
||||
<span>${mod.name}</span>
|
||||
<span>${mod.size}</span>
|
||||
`;
|
||||
modsList.appendChild(modDiv);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error updating server status:', error);
|
||||
document.getElementById('serverStatus').className = 'status-offline';
|
||||
document.getElementById('serverStatus').textContent = 'Offline';
|
||||
}
|
||||
}
|
||||
|
||||
// Initial update and start periodic updates
|
||||
updateServerStatus();
|
||||
setInterval(updateServerStatus, 5000);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
176
server.js
Normal file
176
server.js
Normal file
@ -0,0 +1,176 @@
|
||||
require('dotenv').config();
|
||||
const express = require('express');
|
||||
const { Rcon } = require('rcon-client');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const basicAuth = require('express-basic-auth');
|
||||
const app = express();
|
||||
const port = process.env.PORT || 3000;
|
||||
|
||||
app.use(express.json());
|
||||
|
||||
// Basic Authentication
|
||||
const users = {
|
||||
[process.env.ADMIN_USERNAME]: process.env.ADMIN_PASSWORD
|
||||
};
|
||||
|
||||
app.use(basicAuth({
|
||||
users: users,
|
||||
challenge: true,
|
||||
realm: 'Minecraft Server Control Panel'
|
||||
}));
|
||||
|
||||
app.use(express.static('public'));
|
||||
|
||||
// RCON configuration
|
||||
const config = {
|
||||
host: process.env.RCON_HOST,
|
||||
port: parseInt(process.env.RCON_PORT),
|
||||
password: process.env.RCON_PASSWORD,
|
||||
modsPath: process.env.MODS_PATH,
|
||||
serverPath: process.env.SERVER_PATH
|
||||
};
|
||||
|
||||
let rcon = null;
|
||||
let serverStats = {
|
||||
players: 0,
|
||||
maxPlayers: 0,
|
||||
tps: 20,
|
||||
uptime: 0,
|
||||
mods: []
|
||||
};
|
||||
|
||||
async function connectRcon() {
|
||||
if (rcon === null || !rcon.connected) {
|
||||
rcon = await Rcon.connect({
|
||||
host: config.host,
|
||||
port: config.port,
|
||||
password: config.password
|
||||
});
|
||||
console.log('RCON authenticated');
|
||||
}
|
||||
return rcon;
|
||||
}
|
||||
|
||||
async function getMaxPlayers() {
|
||||
try {
|
||||
const propertiesPath = path.join(config.serverPath || '.', 'server.properties');
|
||||
if (fs.existsSync(propertiesPath)) {
|
||||
const content = fs.readFileSync(propertiesPath, 'utf8');
|
||||
const maxPlayersMatch = content.match(/max-players=(\d+)/);
|
||||
if (maxPlayersMatch) {
|
||||
return parseInt(maxPlayersMatch[1]);
|
||||
}
|
||||
}
|
||||
|
||||
const rcon = await connectRcon();
|
||||
const response = await rcon.send('list');
|
||||
const match = response.match(/\d+\/(\d+)/);
|
||||
if (match) {
|
||||
return parseInt(match[1]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error getting max players:', error);
|
||||
}
|
||||
return 20;
|
||||
}
|
||||
|
||||
function formatBytes(bytes) {
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
const k = 1024;
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||
}
|
||||
|
||||
async function updateServerStats() {
|
||||
try {
|
||||
const rcon = await connectRcon();
|
||||
|
||||
const listResponse = await rcon.send('list');
|
||||
const playerMatch = listResponse.match(/There are (\d+)\/(\d+) players online/);
|
||||
if (playerMatch) {
|
||||
serverStats.players = parseInt(playerMatch[1]);
|
||||
serverStats.maxPlayers = parseInt(playerMatch[2]);
|
||||
} else {
|
||||
serverStats.maxPlayers = await getMaxPlayers();
|
||||
}
|
||||
|
||||
try {
|
||||
const tpsResponse = await rcon.send('tps');
|
||||
const tpsMatch = tpsResponse.match(/\d+\.\d+/);
|
||||
if (tpsMatch) {
|
||||
serverStats.tps = parseFloat(tpsMatch[0]);
|
||||
}
|
||||
} catch (e) {
|
||||
serverStats.tps = 20;
|
||||
}
|
||||
|
||||
try {
|
||||
const modsDir = config.modsPath;
|
||||
if (fs.existsSync(modsDir)) {
|
||||
serverStats.mods = fs.readdirSync(modsDir)
|
||||
.filter(file => file.endsWith('.jar'))
|
||||
.map(file => ({
|
||||
name: file.replace('.jar', ''),
|
||||
size: formatBytes(fs.statSync(path.join(modsDir, file)).size)
|
||||
}));
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error reading mods:', e);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error updating server stats:', error);
|
||||
rcon = null;
|
||||
}
|
||||
}
|
||||
|
||||
app.get('/api/stats', async (req, res) => {
|
||||
try {
|
||||
await updateServerStats();
|
||||
res.json(serverStats);
|
||||
} catch (error) {
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/command', async (req, res) => {
|
||||
const { command } = req.body;
|
||||
|
||||
if (!command) {
|
||||
return res.status(400).json({ success: false, error: 'No command provided' });
|
||||
}
|
||||
|
||||
try {
|
||||
const rcon = await connectRcon();
|
||||
const response = await rcon.send(command);
|
||||
res.json({ success: true, response });
|
||||
} catch (error) {
|
||||
res.status(500).json({ success: false, error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// Validate required environment variables
|
||||
const requiredEnvVars = [
|
||||
'ADMIN_USERNAME',
|
||||
'ADMIN_PASSWORD',
|
||||
'RCON_HOST',
|
||||
'RCON_PORT',
|
||||
'RCON_PASSWORD',
|
||||
'MODS_PATH',
|
||||
'SERVER_PATH'
|
||||
];
|
||||
|
||||
const missingEnvVars = requiredEnvVars.filter(varName => !process.env[varName]);
|
||||
if (missingEnvVars.length > 0) {
|
||||
console.error('Missing required environment variables:', missingEnvVars.join(', '));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Start server
|
||||
app.listen(port, () => {
|
||||
console.log(`Server running at http://localhost:${port}`);
|
||||
updateServerStats(); // Initial stats update
|
||||
setInterval(updateServerStats, 5000); // Update every 5 seconds
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user