first commit
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
node_modules
|
||||||
|
.env
|
112
README.md
Normal file
112
README.md
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
# Minecraft Server Status Checker
|
||||||
|
|
||||||
|
A web application to check the status of Minecraft Java and Bedrock Edition servers. Built with Node.js, Express, and Tailwind CSS, it provides a sleek, modern interface with particle animations and confetti effects for a delightful user experience.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Check status for Minecraft Java and Bedrock Edition servers.
|
||||||
|
- Displays server details like version, player count, MOTD, and more.
|
||||||
|
- Responsive design with Tailwind CSS.
|
||||||
|
- Interactive particle background using tsParticles.
|
||||||
|
- Confetti celebration on successful server status checks.
|
||||||
|
- URL-based server checks for direct access.
|
||||||
|
- Environment variable support for configuration.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- [Node.js](https://nodejs.org/) (v16 or higher)
|
||||||
|
- [npm](https://www.npmjs.com/) (v7 or higher)
|
||||||
|
- Status check binaries (configured via environment variables)
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
1. **Clone the repository**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://git.ssh.surf/hypermc/status-check.git
|
||||||
|
cd status-check
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Install dependencies**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Set up environment variables**:
|
||||||
|
|
||||||
|
Create a `.env` file in the root directory and configure the paths to your status check binaries:
|
||||||
|
|
||||||
|
```env
|
||||||
|
STATUS_CHECK_PATH=/path/to/java-status-check
|
||||||
|
GEYSER_STATUS_CHECK_PATH=/path/to/bedrock-status-check
|
||||||
|
```
|
||||||
|
|
||||||
|
Ensure the binaries are executable and return JSON output as expected by the application.
|
||||||
|
|
||||||
|
4. **Build CSS** (if modifying `style.css`):
|
||||||
|
|
||||||
|
Ensure Tailwind CSS is set up. If you have `tailwindcss` installed globally:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx tailwindcss -i public/css/style.css -o public/css/style.min.css --minify
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively, use the provided `style.min.css` for production.
|
||||||
|
|
||||||
|
5. **Run the application**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
node status_site.js
|
||||||
|
```
|
||||||
|
|
||||||
|
The server will start at `http://localhost:3066`.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
1. Open `http://localhost:3066` in your browser.
|
||||||
|
2. Select the server edition (Java or Bedrock).
|
||||||
|
3. Enter the connection string in the format `host:port` (e.g., `example.com:25565`).
|
||||||
|
4. Click "Check Status" to view the server status.
|
||||||
|
5. Alternatively, use a direct URL like `http://localhost:3066/java/example.com/25565` to check a server.
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
minecraft-server-status-checker/
|
||||||
|
├── public/
|
||||||
|
│ ├── css/
|
||||||
|
│ │ ├── style.css # Tailwind CSS source
|
||||||
|
│ │ └── style.min.css # Minified CSS output
|
||||||
|
│ ├── js/
|
||||||
|
│ │ └── app.js # Client-side JavaScript
|
||||||
|
│ ├── favicon/ # Favicon assets
|
||||||
|
│ └── index.html # Main HTML page
|
||||||
|
├── .env # Environment variables (not tracked)
|
||||||
|
├── status_site.js # Express server
|
||||||
|
├── package.json # Node.js dependencies
|
||||||
|
└── README.md # This file
|
||||||
|
```
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
- **Backend**:
|
||||||
|
- [express](https://expressjs.com/) - Web framework
|
||||||
|
- [dotenv](https://www.npmjs.com/package/dotenv) - Environment variable management
|
||||||
|
- [child_process](https://nodejs.org/api/child_process.html) - For executing status check binaries
|
||||||
|
|
||||||
|
- **Frontend**:
|
||||||
|
- [Tailwind CSS](https://tailwindcss.com/) - Styling
|
||||||
|
- [tsParticles](https://particles.js.org/) - Particle animations
|
||||||
|
- [canvas-confetti](https://www.npmjs.com/package/canvas-confetti) - Confetti effects
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
The application relies on two environment variables:
|
||||||
|
|
||||||
|
https://git.ssh.surf/hypermc/mc-status
|
||||||
|
|
||||||
|
- `STATUS_CHECK_PATH`: Path to the Java Edition status check binary.
|
||||||
|
- `GEYSER_STATUS_CHECK_PATH`: Path to the Bedrock Edition status check binary.
|
||||||
|
|
||||||
|
These binaries should accept `-host` and `-port` arguments and output JSON data.
|
0
package.json
Normal file
0
package.json
Normal file
50
public/css/style.css
Normal file
50
public/css/style.css
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
@import "tailwindcss";
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
transform: scale(1);
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
transform: scale(1.05);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideIn {
|
||||||
|
from {
|
||||||
|
transform: translateY(-20px);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translateY(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-pulse-custom {
|
||||||
|
animation: pulse 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-slide-in {
|
||||||
|
animation: slideIn 0.5s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gradient-bg {
|
||||||
|
background: linear-gradient(135deg, #1e3a8a, #6b21a8, #be185d);
|
||||||
|
}
|
||||||
|
|
||||||
|
.glass {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure gradient text works across browsers */
|
||||||
|
.bg-clip-text {
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
background-clip: text;
|
||||||
|
}
|
1319
public/css/style.min.css
vendored
Normal file
1319
public/css/style.min.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
BIN
public/favicon/android-chrome-192x192.png
Normal file
BIN
public/favicon/android-chrome-192x192.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 38 KiB |
BIN
public/favicon/android-chrome-512x512.png
Normal file
BIN
public/favicon/android-chrome-512x512.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 169 KiB |
BIN
public/favicon/apple-touch-icon.png
Normal file
BIN
public/favicon/apple-touch-icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 35 KiB |
BIN
public/favicon/favicon-16x16.png
Normal file
BIN
public/favicon/favicon-16x16.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 801 B |
BIN
public/favicon/favicon-32x32.png
Normal file
BIN
public/favicon/favicon-32x32.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.3 KiB |
BIN
public/favicon/favicon.ico
Normal file
BIN
public/favicon/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
1
public/favicon/site.webmanifest
Normal file
1
public/favicon/site.webmanifest
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
|
63
public/index.html
Normal file
63
public/index.html
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Minecraft Server Status Checker</title>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/tsparticles@3.5.0/tsparticles.bundle.min.js"></script>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/canvas-confetti@1.9.3/dist/confetti.browser.min.js"></script>
|
||||||
|
<link rel="stylesheet" href="css/style.min.css">
|
||||||
|
<link rel="apple-touch-icon" sizes="180x180" href="/favicon/apple-touch-icon.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="/favicon/favicon-32x32.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href="/favicon/favicon-16x16.png">
|
||||||
|
<link rel="manifest" href="/favicon/site.webmanifest">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body class="bg-gray-900 text-white min-h-screen flex flex-col relative overflow-hidden">
|
||||||
|
<div id="particles-js" class="absolute inset-0 z-0"></div>
|
||||||
|
<header class="relative z-10 bg-gray-800/80 backdrop-blur-md py-6 shadow-md">
|
||||||
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
|
<h1 class="text-4xl font-bold tracking-tight text-center animate-slide-in">
|
||||||
|
My-MC Server Status Check
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<main class="relative z-10 flex-grow max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12 w-full">
|
||||||
|
<div class="bg-gray-800/50 backdrop-blur-md p-8 rounded-2xl shadow-xl max-w-lg mx-auto ring-1 ring-gray-700/50">
|
||||||
|
<form id="serverForm" class="space-y-6">
|
||||||
|
<div class="animate-slide-in" style="animation-delay: 0.1s;">
|
||||||
|
<label for="edition" class="block text-sm font-medium text-gray-300">Server Edition</label>
|
||||||
|
<select id="edition" name="edition"
|
||||||
|
class="mt-2 block w-full bg-gray-700/50 border border-gray-600 rounded-lg shadow-sm focus:ring-blue-500 focus:border-blue-500 text-white transition-all duration-300 hover:bg-gray-600/50 py-2 px-3">
|
||||||
|
<option value="java">Java Edition</option>
|
||||||
|
<option value="bedrock">Bedrock Edition</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="animate-slide-in" style="animation-delay: 0.2s;">
|
||||||
|
<label for="connection" class="block text-sm font-medium text-gray-300">Connection String (host:port)</label>
|
||||||
|
<input type="text" id="connection" name="connection" placeholder="example.com:25565"
|
||||||
|
class="mt-2 block w-full bg-gray-700/50 border border-gray-600 rounded-lg shadow-sm focus:ring-blue-500 focus:border-blue-500 text-white transition-all duration-300 hover:bg-gray-600/50 py-2 px-3"
|
||||||
|
required>
|
||||||
|
</div>
|
||||||
|
<button type="submit"
|
||||||
|
class="w-full bg-blue-600 hover:bg-blue-700 text-white font-semibold py-3 px-4 rounded-lg transition-all duration-300 animate-slide-in shadow-md hover:shadow-lg"
|
||||||
|
style="animation-delay: 0.3s;">
|
||||||
|
Check Status
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<div id="statusResult" class="mt-8 hidden">
|
||||||
|
<h2 class="text-2xl font-semibold text-blue-400 animate-slide-in">Server Status</h2>
|
||||||
|
<div id="statusContent" class="mt-4 p-6 bg-gray-700/50 rounded-lg text-gray-200 ring-1 ring-gray-600/50"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<footer class="relative z-10 bg-gray-800/80 backdrop-blur-md py-4 shadow-md">
|
||||||
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
|
||||||
|
<p class="text-gray-400 text-sm">© 2025 Minecraft Server Status Checker</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
<script src="js/app.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
105
public/js/app.js
Normal file
105
public/js/app.js
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
// Initialize tsParticles
|
||||||
|
tsParticles.load("particles-js", {
|
||||||
|
particles: {
|
||||||
|
number: { value: 50, density: { enable: true, value_area: 800 } },
|
||||||
|
color: { value: ["#60a5fa", "#a855f7", "#ec4899"] },
|
||||||
|
shape: { type: "circle" },
|
||||||
|
opacity: { value: 0.5, random: true },
|
||||||
|
size: { value: 3, random: true },
|
||||||
|
move: { enable: true, speed: 0.5, direction: "none", random: true, out_mode: "out" },
|
||||||
|
},
|
||||||
|
interactivity: {
|
||||||
|
events: { onhover: { enable: true, mode: "repulse" }, onclick: { enable: true, mode: "push" } },
|
||||||
|
modes: { repulse: { distance: 100, duration: 0.4 }, push: { particles_nb: 4 } },
|
||||||
|
},
|
||||||
|
retina_detect: true
|
||||||
|
});
|
||||||
|
|
||||||
|
// Confetti function
|
||||||
|
function launchConfetti() {
|
||||||
|
confetti({
|
||||||
|
particleCount: 100,
|
||||||
|
spread: 70,
|
||||||
|
origin: { y: 0.6 },
|
||||||
|
colors: ['#60a5fa', '#a855f7', '#ec4899']
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Form submission handler
|
||||||
|
document.getElementById('serverForm').addEventListener('submit', async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const edition = document.getElementById('edition').value;
|
||||||
|
const connection = document.getElementById('connection').value;
|
||||||
|
const [host, port] = connection.split(':');
|
||||||
|
|
||||||
|
if (!host || !port) {
|
||||||
|
alert('Please enter a valid connection string (host:port)');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const statusResult = document.getElementById('statusResult');
|
||||||
|
const statusContent = document.getElementById('statusContent');
|
||||||
|
statusResult.classList.add('hidden');
|
||||||
|
statusContent.innerHTML = '<p class="animate-pulse">Checking...</p>';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/${edition}/${host}/${port}`);
|
||||||
|
if (!response.ok) throw new Error('Request failed');
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
statusResult.classList.remove('hidden');
|
||||||
|
if (result.isOnline) {
|
||||||
|
launchConfetti();
|
||||||
|
const { data } = result;
|
||||||
|
const statusData = {};
|
||||||
|
|
||||||
|
if (edition === 'java') {
|
||||||
|
if (data.version?.name?.clean) statusData['Version'] = data.version.name.clean;
|
||||||
|
if (data.version?.protocol) statusData['Protocol'] = data.version.protocol;
|
||||||
|
if (data.players?.online != null) statusData['Players Online'] = data.players.online;
|
||||||
|
if (data.players?.max != null) statusData['Max Players'] = data.players.max;
|
||||||
|
if (data.motd?.clean) statusData['MOTD'] = data.motd.clean;
|
||||||
|
if (data.favicon) statusData['Favicon'] = 'Present';
|
||||||
|
if (data.srv_record) statusData['SRV Record'] = JSON.stringify(data.srv_record);
|
||||||
|
if (data.mods) statusData['Mods'] = JSON.stringify(data.mods);
|
||||||
|
} else {
|
||||||
|
if (data.version) statusData['Version'] = data.version;
|
||||||
|
if (data.protocol_version) statusData['Protocol'] = data.protocol_version;
|
||||||
|
if (data.online_players != null) statusData['Players Online'] = data.online_players;
|
||||||
|
if (data.max_players != null) statusData['Max Players'] = data.max_players;
|
||||||
|
if (data.motd) statusData['MOTD'] = data.motd.clean;
|
||||||
|
if (data.gamemode) statusData['Gamemode'] = data.gamemode;
|
||||||
|
if (data.server_id) statusData['Server ID'] = data.server_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
statusContent.innerHTML = `
|
||||||
|
<p><strong class="text-blue-400">Status:</strong> <span class="text-green-400">Online</span></p>
|
||||||
|
${Object.entries(statusData).map(([key, value]) => `
|
||||||
|
<p><strong class="text-blue-400">${key}:</strong> ${value}</p>
|
||||||
|
`).join('')}
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
statusContent.innerHTML = `
|
||||||
|
<p><strong class="text-blue-400">Status:</strong> <span class="text-red-400">Offline</span></p>
|
||||||
|
<p><strong class="text-blue-400">Error:</strong> Server is not reachable</p>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
statusResult.classList.remove('hidden');
|
||||||
|
statusContent.innerHTML = `
|
||||||
|
<p><strong class="text-blue-400">Status:</strong> <span class="text-red-400">Error</span></p>
|
||||||
|
<p><strong class="text-blue-400">Error:</strong> An error occurred while checking the server status</p>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle URL-based checks
|
||||||
|
const path = window.location.pathname.split('/');
|
||||||
|
if (path[1] && path[2] && path[3]) {
|
||||||
|
const edition = path[1];
|
||||||
|
const host = path[2];
|
||||||
|
const port = path[3];
|
||||||
|
document.getElementById('edition').value = edition;
|
||||||
|
document.getElementById('connection').value = `${host}:${port}`;
|
||||||
|
document.getElementById('serverForm').dispatchEvent(new Event('submit'));
|
||||||
|
}
|
67
status-site.js
Normal file
67
status-site.js
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const { promisify } = require('util');
|
||||||
|
const { exec } = require('child_process');
|
||||||
|
const app = express();
|
||||||
|
const port = 3066;
|
||||||
|
require('dotenv').config();
|
||||||
|
|
||||||
|
const execPromise = promisify(exec);
|
||||||
|
|
||||||
|
async function checkConnectionStatus(hostname, port) {
|
||||||
|
try {
|
||||||
|
const command = `${process.env.STATUS_CHECK_PATH} -host ${hostname} -port ${port}`;
|
||||||
|
const { stdout, stderr } = await execPromise(command);
|
||||||
|
if (stderr) {
|
||||||
|
return { isOnline: false, error: stderr };
|
||||||
|
}
|
||||||
|
const data = JSON.parse(stdout);
|
||||||
|
return { isOnline: true, data };
|
||||||
|
} catch (error) {
|
||||||
|
return { isOnline: false, error: error.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkGeyserStatus(hostname, port) {
|
||||||
|
try {
|
||||||
|
const command = `${process.env.GEYSER_STATUS_CHECK_PATH} -host ${hostname} -port ${port}`;
|
||||||
|
const { stdout, stderr } = await execPromise(command);
|
||||||
|
if (stderr) {
|
||||||
|
return { isOnline: false, error: stderr };
|
||||||
|
}
|
||||||
|
const data = JSON.parse(stdout);
|
||||||
|
return { isOnline: true, data };
|
||||||
|
} catch (error) {
|
||||||
|
return { isOnline: false, error: error.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
app.use(express.static('public'));
|
||||||
|
|
||||||
|
app.get('/', (req, res) => {
|
||||||
|
res.sendFile(__dirname + '/public/index.html');
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/java/:host/:port', async (req, res) => {
|
||||||
|
const { host, port } = req.params;
|
||||||
|
try {
|
||||||
|
const result = await checkConnectionStatus(host, port);
|
||||||
|
res.json(result);
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ isOnline: false, error: `Server error: ${error.message}` });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/bedrock/:host/:port', async (req, res) => {
|
||||||
|
const { host, port } = req.params;
|
||||||
|
try {
|
||||||
|
const result = await checkGeyserStatus(host, port);
|
||||||
|
console.log(result)
|
||||||
|
res.json(result);
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({ isOnline: false, error: `Server error: ${error.message}` });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.listen(port, () => {
|
||||||
|
console.log(`Server running at http://localhost:${port}`);
|
||||||
|
});
|
Reference in New Issue
Block a user