Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
cd61903
fix(api): Rename include to actual file name
OmarSherif06 Jun 28, 2025
3775c93
feat(api): Create `getAllInfo` endpoint
OmarSherif06 Jun 28, 2025
03cbbe8
feat(api): Delete old files when
OmarSherif06 Jun 28, 2025
bcfeb76
fix(api): Correctly detect Minecraft version for Fabric servers
OmarSherif06 Jun 28, 2025
2482b59
chore: Merge pull request #6 from DEVUCP/api
DEVUCP Jul 5, 2025
07cff76
fix(api): Correct argument typo for `startServer`
DEVUCP Jul 6, 2025
93368b4
fix(api): Remove 'M' from memory in config
DEVUCP Jul 6, 2025
9bae5a0
feat(api): Add `serverStatus` to info endpoint json package
DEVUCP Jul 6, 2025
52a9166
feat(frontend): Create context provider which calls info and properti…
DEVUCP Jul 6, 2025
24916b5
feat(frontend): Create `card.js` components
DEVUCP Jul 6, 2025
cc0cdb3
feat(frontend): Create `InfoTab` component
DEVUCP Jul 6, 2025
371ac2b
feat(frontend): Use `InfoTab` component in `ServerWindow`
DEVUCP Jul 6, 2025
486922f
feat(frontend): Rework `startstop` button
DEVUCP Jul 6, 2025
acf0f4f
fix(frontend): Null values when null
DEVUCP Jul 6, 2025
a1e3cf8
refactor(frontend): Remove `monitor` util and use `serverDataContext`…
DEVUCP Jul 6, 2025
f385dd8
fix(scripts)!: windows script fix
OmarSherif06 Jul 11, 2025
49f0467
feat(frontend): Create and use custom tab components instead of MUI
DEVUCP Jul 6, 2025
6e95a56
fix(api): Include IP printing in non-debug only
DEVUCP Jul 12, 2025
3dd6236
feat(api): Create and use logger middleware
DEVUCP Jul 12, 2025
afa3bf0
refactor(api): Change toggleProperty to updateProperty for more versa…
DEVUCP Jul 12, 2025
43b5cfa
feat(frontend): Create `Switch` component for later use
DEVUCP Jul 12, 2025
6efdf86
feat(frontend): Create properties tab and use it
DEVUCP Jul 12, 2025
18d518c
feat(installer): Create installer GUI app using python and build to e…
DEVUCP Jul 12, 2025
b1f432a
fix(api): Add `getOnlinePlayers` to the getAllInfo endpoint json resp…
DEVUCP Jul 12, 2025
9bf2c97
fix(api): Include missing import
DEVUCP Jul 18, 2025
29ee1aa
fix(api): Correct `getOnlinePlayers` output
DEVUCP Jul 18, 2025
a810d8a
fix(frontend): Correct display for playerCount
DEVUCP Jul 18, 2025
3aac034
fix(api): Include `server,properties` to preserveList in installations
DEVUCP Aug 6, 2025
3608ce4
feat(frontend): Create new Theme
DEVUCP Aug 6, 2025
2fa0062
feat(frontend): Rework Navbar to have share dashboard and minecraft i…
DEVUCP Aug 6, 2025
9b62ab4
fix(frontend): Use publicIP if available when localhost is used for e…
DEVUCP Aug 6, 2025
c98b58f
fix(frontend): Use correct input type for gamemode and port properties
DEVUCP Aug 6, 2025
4532586
feat(frontend): Create `VersionTab` component
DEVUCP Aug 6, 2025
96429e7
feat(frontend): Let switches able to be null
DEVUCP Aug 6, 2025
bd12a1d
fix(frontend): Use accurate ip and port not dependant on url entirely
DEVUCP Aug 6, 2025
b46297e
style(frontend): share button border blah
DEVUCP Aug 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions api/app.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const express = require('express');
const cors = require('cors');
const { limiter } = require('./middleware/limiter.middleware');
const logger = require('./middleware/logger.middleware');

const app = express();

Expand All @@ -12,6 +13,7 @@ const infoRoutes = require('./routes/info.routes');

app.use(cors());
app.use(limiter)
app.use(logger);
app.use(express.json());

app.use('/server', serverRoutes);
Expand Down
32 changes: 14 additions & 18 deletions api/controllers/info.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,8 @@ async function playerCount(req, res) {

async function getUpTime(req, res) {
try {
if (infoService.getStartTime() === null) {
res.status(200).send({ uptime: "0s" });
return;
}

const ms = Date.now() - infoService.getStartTime();

const totalSeconds = Math.floor(ms / 1000);
const hours = Math.floor(totalSeconds / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60);
const seconds = totalSeconds % 60;

let formatted = [];
if (hours > 0) formatted.push(`${hours}h`);
if (minutes > 0 || hours > 0) formatted.push(`${minutes}m`);
formatted.push(`${seconds}s`);

res.status(200).send({ uptime: formatted.join(" ") });
res.status(200).send(await infoService.getUpTime());
} catch (error) {
console.error(error);
res.status(500).send("error.. " + error.message);
Expand Down Expand Up @@ -80,11 +64,23 @@ async function getPlatform(req, res) {
}
}

async function getAllInfo(req, res) {
try {
let serverProcess = serverService.getServerProcess();
let jarPath = consts.serverDirectory + "/" + consts.serverName
res.status(200).send(await infoService.getInfo(serverProcess, jarPath, consts.serverDirectory));
} catch (error) {
console.error(error);
res.status(500).send("error.. " + error.message);
}
}

module.exports = {
playerCount,
getUpTime,
getMemoryUsage,
getWorldSize,
getVersion,
getPlatform
getPlatform,
getAllInfo
}
4 changes: 2 additions & 2 deletions api/controllers/installations.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ const { Sema } = require('async-sema');
const downloadSema = new Sema(1);

async function downloadServer(req, res) {
await downloadSema.acquire();

await downloadSema.acquire();
try {
await installationsService.downloadRouter(req.params.platform, req.params.version);
res.status(201).send('Downloaded Successfully');
Expand Down
6 changes: 3 additions & 3 deletions api/controllers/properties.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ const { Sema } = require('async-sema');

let togglePropertySema = new Sema(1);

async function toggleProperty(req, res) {
async function updateProperty(req, res) {
togglePropertySema.acquire();

try {
console.log(req.params.property);
await propertiesService.updateProperty(req.params.property, true);
await propertiesService.updateProperty(req.params.property, req.params.newvalue);
res.status(200).send("done");
} catch(error) {
console.error(error);
Expand Down Expand Up @@ -151,7 +151,7 @@ async function modifyBannedIPs(req, res) {


module.exports = {
toggleProperty,
updateProperty,
allocateRam,
serverConfig,
getWhitelist,
Expand Down
18 changes: 18 additions & 0 deletions api/middleware/logger.middleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
module.exports = (req, res, next) => {
const colors = {
GET: '\x1b[32m', // Green
POST: '\x1b[33m', // Yellow
PUT: '\x1b[34m', // Blue
PATCH: '\x1b[35m',
DELETE: '\x1b[31m', // Red
RESET: '\x1b[0m' // Reset color
};
const color = colors[req.method] || colors.RESET;
// console.log(
// `${color}[${new Date().toISOString()}] ${req.method} ${req.url} - IP: ${req.ip} - User-Agent: ${req.headers['user-agent']}${colors.RESET}`
// );
console.log(
`${color}[${new Date().toISOString()}] ${req.method} ${req.url} - IP: ${req.ip}${colors.RESET}`
);
next();
};
5 changes: 4 additions & 1 deletion api/routes/info.routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@ const {
getMemoryUsage,
getWorldSize,
getVersion,
getPlatform
getPlatform,
getAllInfo
} = require('../controllers/info.controller');

router.get('/', getAllInfo);

router.get('/player-count', playerCount);

router.get('/uptime', getUpTime);
Expand Down
4 changes: 2 additions & 2 deletions api/routes/properties.routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const router = express.Router();
const propertiesServices = require('../services/properties.service');

const {
toggleProperty,
updateProperty,
allocateRam,
serverConfig,
getWhitelist,
Expand All @@ -25,7 +25,7 @@ router.get('/', async (req, res) => {
}
})

router.put('/toggle/:property', toggleProperty);
router.put('/update/:property/:newvalue', updateProperty);

router.put('/allocate-ram/:mb', allocateRam);

Expand Down
106 changes: 95 additions & 11 deletions api/services/info.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ const fs = require('fs');
const path = require('path');
const pidusage = require('pidusage')
const AdmZip = require('adm-zip');
const consts = require('../consts');
const serverService = require("./server.service");
const propertiesService = require("./properties.service");

let startTime = null;

Expand Down Expand Up @@ -56,13 +59,11 @@ function getDirectorySize(folderPath) {
return sizeInMB;
}

function getPlatform(jarPath) {
function getPlatform(jarPath = consts.serverDirectory + "/" + consts.serverName) {
const zip = new AdmZip(jarPath);
const entries = zip.getEntries();
const names = entries.map(e => e.entryName);

console.log(names);

const has = (file) => names.includes(file);

// Detect Paper
Expand All @@ -88,19 +89,20 @@ function getPlatform(jarPath) {
}

function getVersion(jarPath) {
if (getPlatform(jarPath) == "Fabric") {
console.error("Unable to get version from fabric servers");
return "Unable to fetch version";
}


const zip = new AdmZip(jarPath);
const entries = zip.getEntries();


if (getPlatform(jarPath) == "Fabric") {
const installEntry = entries.find(entry => entry.entryName === 'install.properties');
const text = zip.readAsText(installEntry);
return text.split("game-version=")[1];
}

const versionEntry = entries.find(e => e.entryName === "version.json");
if (versionEntry) {
try {
const content = JSON.parse(zip.readAsText(versionEntry));
console.log(content);
return content.name || content.id || null;
} catch (err) {
console.warn("Failed to parse version.json:", err.message);
Expand All @@ -117,6 +119,86 @@ function getVersion(jarPath) {
return null;
}

async function getUpTime() {
if (getStartTime() === null)
return { uptime: "0s" };

const ms = Date.now() - getStartTime();

const totalSeconds = Math.floor(ms / 1000);
const hours = Math.floor(totalSeconds / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60);
const seconds = totalSeconds % 60;

let formatted = [];
if (hours > 0) formatted.push(`${hours}h`);
if (minutes > 0 || hours > 0) formatted.push(`${minutes}m`);
formatted.push(`${seconds}s`);

return { uptime: formatted.join(" ") };
}

async function getInfo(serverProcess, jarPath, folderPath) {
let memoryUsage = null;
let platform = null;
let version = null;
let directorySizeMB = null;
let serverStatus = null;
let playerCount = null;

try {
memoryUsage = await getMemoryUsage(serverProcess);
} catch (err) {
console.warn('Failed to get memory usage:', err.message);
}

try {
platform = getPlatform(jarPath);
} catch (err) {
console.warn('Failed to get platform:', err.message);
}

try {
version = getVersion(jarPath);
} catch (err) {
console.warn('Failed to get version:', err.message);
}

try {
directorySizeMB = getDirectorySize(folderPath);
directorySizeMB = Math.round(directorySizeMB * 100) / 100;
} catch (err) {
console.warn('Failed to get directory size:', err.message);
}

try {
uptime = await getUpTime();
} catch (err) {
console.warn('Failed to calculate uptime:', err.message);
}

try {
serverStatus = serverService.isServerStarting();
} catch (err) {
console.warn('Failed to get server status:', err.message);
}

try {
playerCount = await propertiesService.getOnlinePlayers();
} catch (err) {
console.warn('Failed to get player count:', err.message);
}

return {
memoryUsage,
platform,
version,
directorySizeMB,
uptime: uptime.uptime,
status: serverStatus,
playerCount: playerCount
};
}

module.exports = {
startCounting,
Expand All @@ -125,5 +207,7 @@ module.exports = {
getMemoryUsage,
getDirectorySize,
getPlatform,
getVersion
getVersion,
getInfo,
getUpTime
}
55 changes: 51 additions & 4 deletions api/services/installations.service.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,56 @@
const urlFetcher = require("../utils/platformURLFetcherUtil");
const urlFetcher = require("../utils/url_fetcher.util");
const infoService = require('../services/info.service');
const consts = require('../consts');
const { writeDownloadedFile } = require("../utils/installations.util");
const fs = require('fs/promises');
const path = require('path');

const preserveList = [
"world",
"world_nether",
"world_the_end",
"banned-ips.json",
"banned-players.json",
"server.properties",
"ops.json",
"whitelist.json"
];

async function purgeServer(preserveList) {
let folderPath = consts.serverDirectory;
try {
const entries = await fs.readdir(folderPath, { withFileTypes: true });

for (const entry of entries) {
if (preserveList.includes(entry.name)) continue;

const fullPath = path.join(folderPath, entry.name);
if (entry.isDirectory())
await fs.rm(fullPath, { recursive: true, force: true });
else
await fs.unlink(fullPath);
}

console.log(`Deleted everything in ${folderPath} except preserved items.`);
} catch (err) {
console.error(`Error cleaning folder ${folderPath}:`, err.message);
}
}

async function downloadRouter(platform, version) {
const oldPlatform = infoService.getPlatform();

const comingFromModded = oldPlatform === "fabric" || oldPlatform === "forge";
const currentPreserveList = comingFromModded
? preserveList.slice(3) // delete worlds
: preserveList; // keep everything

if (oldPlatform != platform)
await purgeServer(currentPreserveList);

let response;
try {
switch(platform) {
switch (platform) {
case "vanilla":
response = await fetch(await urlFetcher.fetchVanillaURL(version));
break;
Expand All @@ -17,9 +63,10 @@ async function downloadRouter(platform, version) {
case "forge":
response = await fetch(await urlFetcher.fetchForgeURL(version));
break;
case _:
throw new Error(`Invalid platform --> ${platform}`)
default:
throw new Error(`Invalid platform --> ${platform}`);
}

await writeDownloadedFile(response, version, platform.toUpperCase());
} catch (error) {
console.error(error);
Expand Down
Loading
Loading