initial commit
This commit is contained in:
commit
4ae0582464
10 changed files with 489 additions and 0 deletions
21
core/globals.ts
Normal file
21
core/globals.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import type {MinecraftServer} from "~/types/MinecraftServer";
|
||||||
|
import type {SettingsJsonFile} from "~/types/SettingsJsonFile";
|
||||||
|
import {saveJsonFile} from "~/util/jsonLoader";
|
||||||
|
|
||||||
|
export namespace environment{
|
||||||
|
export namespace paths{
|
||||||
|
export let data: string = "/home/weexnes/.mcservermanager";
|
||||||
|
export let servers: string = "/home/weexnes/.mcservermanager/servers";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export namespace files{
|
||||||
|
export let settings: string = paths.data + "/settings.json";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export let settings: SettingsJsonFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
232
pages/index.vue
Normal file
232
pages/index.vue
Normal file
|
@ -0,0 +1,232 @@
|
||||||
|
<template>
|
||||||
|
<div class="flex flex-col items-center justify-center py-16 px-6">
|
||||||
|
<h1 class="text-4xl font-bold text-center mb-6">Minecraft Servers</h1>
|
||||||
|
<div class="grid md:grid-cols-3 gap-6 w-full max-w-5xl mb-8">
|
||||||
|
<div v-for="server in settings.servers" class="card bg-base-100 shadow-2xl p-6" @click="editServerDialog(server)">
|
||||||
|
<h2 class="text-xl font-bold text-center">
|
||||||
|
{{ server.name }}
|
||||||
|
</h2>
|
||||||
|
<div class="mt-2 text-sm">
|
||||||
|
<p><strong>Version:</strong> {{ server.version }}</p>
|
||||||
|
<p><strong>Port</strong> {{ server.port }}</p>
|
||||||
|
<p><strong>Slots:</strong> {{ server.maxPlayers }}</p>
|
||||||
|
<p><strong>Max Memory:</strong> {{ server.maxMemory }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card bg-base-100 shadow-2xl p-6 cursor-pointer" onclick="addServerModal.showModal()">
|
||||||
|
<h2 class="text-xl font-bold text-center">
|
||||||
|
Add Server
|
||||||
|
</h2>
|
||||||
|
<div class="mt-2 text-sm flex justify-center">
|
||||||
|
<Icon name="material-symbols:add" class="icon" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<dialog id="addServerModal" class="modal">
|
||||||
|
<div class="modal-box">
|
||||||
|
<h3 class="text-lg font-bold">Add a new Server</h3>
|
||||||
|
|
||||||
|
<label class="form-control w-full max-w-xs">
|
||||||
|
<div class="label">
|
||||||
|
<span class="label-text">Server Name</span>
|
||||||
|
</div>
|
||||||
|
<input v-model="addDialog.newServer.name" type="text" placeholder="Type here" class="input input-bordered w-full max-w-xs" />
|
||||||
|
</label>
|
||||||
|
<label class="form-control w-full max-w-xs">
|
||||||
|
<div class="label">
|
||||||
|
<span class="label-text">Port</span>
|
||||||
|
</div>
|
||||||
|
<input v-model="addDialog.newServer.port" type="text" placeholder="Type here" class="input input-bordered w-full max-w-xs" />
|
||||||
|
</label>
|
||||||
|
<label class="form-control w-full max-w-xs">
|
||||||
|
<div class="label">
|
||||||
|
<span class="label-text">Max Players</span>
|
||||||
|
</div>
|
||||||
|
<input v-model="addDialog.newServer.maxPlayers" type="text" placeholder="Type here" class="input input-bordered w-full max-w-xs" />
|
||||||
|
</label>
|
||||||
|
<label class="form-control w-full max-w-xs">
|
||||||
|
<div class="label">
|
||||||
|
<span class="label-text">Version</span>
|
||||||
|
</div>
|
||||||
|
<input v-model="addDialog.newServer.version" type="text" placeholder="Type here" class="input input-bordered w-full max-w-xs" />
|
||||||
|
</label>
|
||||||
|
<label class="form-control w-full max-w-xs">
|
||||||
|
<div class="label">
|
||||||
|
<span class="label-text">Jar Download URL</span>
|
||||||
|
</div>
|
||||||
|
<input v-model="addDialog.newServer.jar_url" type="text" placeholder="Type here" class="input input-bordered w-full max-w-xs" />
|
||||||
|
</label>
|
||||||
|
<label class="form-control w-full max-w-xs">
|
||||||
|
<div class="label">
|
||||||
|
<span class="label-text"></span>
|
||||||
|
</div>
|
||||||
|
<div class="dropdown dropdown-bottom dropdown-center">
|
||||||
|
<div tabindex="0" role="button" class="btn m-1">Difficuly: {{addDialog.newServer.difficulty}}</div>
|
||||||
|
<ul tabindex="0" class="dropdown-content menu bg-base-100 rounded-box z-1 w-52 p-2 shadow-sm">
|
||||||
|
<li @click="setNewServerDifficulty('peaceful')"><a>Peaceful</a></li>
|
||||||
|
<li @click="setNewServerDifficulty('easy')"><a>Easy</a></li>
|
||||||
|
<li @click="setNewServerDifficulty('normal')"><a>Normal</a></li>
|
||||||
|
<li @click="setNewServerDifficulty('hard')"><a>Hard</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</label>
|
||||||
|
<label class="form-control w-full max-w-xs">
|
||||||
|
<div class="label">
|
||||||
|
<span class="label-text">Spawn Protection</span>
|
||||||
|
</div>
|
||||||
|
<input v-model="addDialog.newServer.spawnProtection" type="text" placeholder="Type here" class="input input-bordered w-full max-w-xs" />
|
||||||
|
</label>
|
||||||
|
<label class="form-control w-full max-w-xs">
|
||||||
|
<div class="label">
|
||||||
|
<span class="label-text">Render Distance</span>
|
||||||
|
</div>
|
||||||
|
<input v-model="addDialog.newServer.viewDistance" type="text" placeholder="Type here" class="input input-bordered w-full max-w-xs" />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
|
||||||
|
<label class="form-control w-full max-w-xs">
|
||||||
|
<div class="label">
|
||||||
|
<span class="label-text">JVM Min Memory</span>
|
||||||
|
</div>
|
||||||
|
<input v-model="addDialog.newServer.minMemory" type="text" placeholder="Type here" class="input input-bordered w-full max-w-xs" />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
|
||||||
|
<label class="form-control w-full max-w-xs">
|
||||||
|
<div class="label">
|
||||||
|
<span class="label-text">JVM Max Memory</span>
|
||||||
|
</div>
|
||||||
|
<input v-model="addDialog.newServer.maxMemory" type="text" placeholder="Type here" class="input input-bordered w-full max-w-xs" />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="modal-action">
|
||||||
|
<form method="dialog">
|
||||||
|
<button class="btn" @click="addServer">Add</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
|
|
||||||
|
<dialog id="editServerModal" class="modal">
|
||||||
|
<div class="modal-box w-11/12 max-w-5xl">
|
||||||
|
<h3 class="text-lg font-bold">{{ editDialog.editedServer.name }}</h3>
|
||||||
|
<p class="py-4"> blablabla </p>
|
||||||
|
<div class="modal-action">
|
||||||
|
<form method="dialog">
|
||||||
|
<!-- if there is a button, it will close the modal -->
|
||||||
|
<button @click="deleteServer" class="btn">Delete</button>
|
||||||
|
<button @click="startServer" class="btn">Save Changes</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</dialog>
|
||||||
|
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
import type {MinecraftServer} from "~/types/MinecraftServer";
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
|
||||||
|
const settings = reactive({
|
||||||
|
servers: [] as MinecraftServer[],
|
||||||
|
})
|
||||||
|
|
||||||
|
const addDialog = reactive({
|
||||||
|
newServer: {} as MinecraftServer
|
||||||
|
})
|
||||||
|
|
||||||
|
const editDialog = reactive({
|
||||||
|
editedServer: {} as MinecraftServer
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const getServers = async () => {
|
||||||
|
try{
|
||||||
|
settings.servers = await $fetch('/api/getServers')
|
||||||
|
settings.servers.forEach((server) => {
|
||||||
|
console.log(server.name)
|
||||||
|
})
|
||||||
|
}catch(error){
|
||||||
|
console.error(`Error fetch: ${error}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const editServerDialog = async (server: MinecraftServer) => {
|
||||||
|
editDialog.editedServer = server
|
||||||
|
// @ts-ignore
|
||||||
|
editServerModal.showModal()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const addServer = async () => {
|
||||||
|
if(!settings.servers.some(server => server.name === addDialog.newServer.name)){
|
||||||
|
addDialog.newServer.logs = ["Server Created via Webui"]
|
||||||
|
settings.servers.push(addDialog.newServer)
|
||||||
|
await syncServers()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteServer = async () => {
|
||||||
|
let index = settings.servers.findIndex(item => item.name === editDialog.editedServer.name);
|
||||||
|
settings.servers.splice(index, 1);
|
||||||
|
await syncServers()
|
||||||
|
}
|
||||||
|
|
||||||
|
const setNewServerDifficulty = async (newDiff: "peaceful" | "easy" | "normal" | "hard") => {
|
||||||
|
addDialog.newServer.difficulty = newDiff;
|
||||||
|
}
|
||||||
|
|
||||||
|
const syncServers = async () => {
|
||||||
|
try {
|
||||||
|
const response = await axios.post('/api/setServers', {
|
||||||
|
serverList: settings.servers
|
||||||
|
});
|
||||||
|
console.log(response.data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error `, error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const startServer = async () => {
|
||||||
|
try {
|
||||||
|
const response = await axios.post('/api/startServer', {
|
||||||
|
name: editDialog.editedServer.name
|
||||||
|
});
|
||||||
|
console.log(response.data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error `, error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
onMounted(async()=>{
|
||||||
|
await getServers()
|
||||||
|
})
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.icon {
|
||||||
|
width: 5rem; /* You can adjust this value based on your needs */
|
||||||
|
height: 5rem; /* Set both width and height */
|
||||||
|
object-fit: contain; /* Keep the aspect ratio of the icon */
|
||||||
|
}
|
||||||
|
</style>
|
10
server/api/getServers.ts
Normal file
10
server/api/getServers.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import { defineEventHandler, getCookie, createError } from 'h3';
|
||||||
|
import {environment} from "~/core/globals";
|
||||||
|
|
||||||
|
export default defineEventHandler(async (event) => {
|
||||||
|
try {
|
||||||
|
return environment.settings.servers;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching CPU info:', error);
|
||||||
|
}
|
||||||
|
});
|
18
server/api/setServers.ts
Normal file
18
server/api/setServers.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import {environment} from "~/core/globals";
|
||||||
|
import {saveJsonFile} from "~/util/jsonLoader";
|
||||||
|
|
||||||
|
export default defineEventHandler(async (event) => {
|
||||||
|
const body = await readBody(event);
|
||||||
|
const { serverList } = body;
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log("Recived server list");
|
||||||
|
environment.settings.servers = serverList;
|
||||||
|
saveJsonFile()
|
||||||
|
|
||||||
|
return { status: 'success' };
|
||||||
|
} catch (error) {
|
||||||
|
return { status: 'error', error: error };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
44
server/api/startServer.ts
Normal file
44
server/api/startServer.ts
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
import {environment} from "~/core/globals";
|
||||||
|
import {saveJsonFile} from "~/util/jsonLoader";
|
||||||
|
import {MinecraftServer} from "~/types/MinecraftServer";
|
||||||
|
import {execa} from "execa";
|
||||||
|
|
||||||
|
|
||||||
|
export default defineEventHandler(async (event) => {
|
||||||
|
const body = await readBody(event);
|
||||||
|
const server_name: string = body.name
|
||||||
|
|
||||||
|
const server = environment.settings.servers.find(server => server.name == server_name);
|
||||||
|
|
||||||
|
if(!server) {
|
||||||
|
return { message: "Server with name " + server_name + " does not exist" };
|
||||||
|
}
|
||||||
|
|
||||||
|
const workingDir = environment.paths.servers + "/" + server.name;
|
||||||
|
const jarPath = environment.paths.servers + "/" + server.name + "/server.jar";
|
||||||
|
|
||||||
|
try {
|
||||||
|
server.process = execa('java', ['-jar', jarPath, 'nogui'], {
|
||||||
|
cwd: workingDir,
|
||||||
|
});
|
||||||
|
|
||||||
|
server.process.stdout.on('data', (data: string) => {
|
||||||
|
const logData = data.toString();
|
||||||
|
console.log(logData);
|
||||||
|
});
|
||||||
|
|
||||||
|
server.process.stderr.on('data', (data: string) => {
|
||||||
|
const errorData = data.toString();
|
||||||
|
console.log(errorData);
|
||||||
|
});
|
||||||
|
|
||||||
|
server.process.on('exit', (code: string) => {
|
||||||
|
console.log(`Minecraft server process exited with code ${code}`);
|
||||||
|
server.process = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
return { message: "Minecraft server started." };
|
||||||
|
} catch (error: any) {
|
||||||
|
return { message: "Failed to start Minecraft server.", error: error.message };
|
||||||
|
}
|
||||||
|
});
|
16
server/plugins/init.ts
Normal file
16
server/plugins/init.ts
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
import {environment} from "~/core/globals";
|
||||||
|
import {loadJsonFile, removeServerByName, saveJsonFile, createPathIfNotExists} from "~/util/jsonLoader";
|
||||||
|
|
||||||
|
export default defineNitroPlugin((nitroApp) => {
|
||||||
|
console.log("Loading config...")
|
||||||
|
for (const key of Object.keys(environment.paths)) {
|
||||||
|
createPathIfNotExists(environment.paths[key as keyof typeof environment.paths]);
|
||||||
|
}
|
||||||
|
loadJsonFile()
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
3
tailwind.config.js
Normal file
3
tailwind.config.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
module.exports = {
|
||||||
|
plugins: [require('daisyui')],
|
||||||
|
};
|
27
types/MinecraftServer.ts
Normal file
27
types/MinecraftServer.ts
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import type {ResultPromise} from "execa";
|
||||||
|
|
||||||
|
export interface MinecraftServer {
|
||||||
|
name: string;
|
||||||
|
port: number;
|
||||||
|
maxPlayers: number;
|
||||||
|
version: string;
|
||||||
|
jar_url: string;
|
||||||
|
|
||||||
|
isRunning: boolean;
|
||||||
|
currentPlayers: number;
|
||||||
|
|
||||||
|
|
||||||
|
difficulty: "peaceful" | "easy" | "normal" | "hard";
|
||||||
|
spawnProtection: number;
|
||||||
|
viewDistance: number;
|
||||||
|
|
||||||
|
|
||||||
|
minMemory: number;
|
||||||
|
maxMemory: number;
|
||||||
|
|
||||||
|
logs: string[];
|
||||||
|
|
||||||
|
|
||||||
|
process: any;
|
||||||
|
|
||||||
|
}
|
6
types/SettingsJsonFile.ts
Normal file
6
types/SettingsJsonFile.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import type {MinecraftServer} from "~/types/MinecraftServer";
|
||||||
|
|
||||||
|
export interface SettingsJsonFile {
|
||||||
|
eula: boolean;
|
||||||
|
servers: MinecraftServer[];
|
||||||
|
}
|
112
util/jsonLoader.ts
Normal file
112
util/jsonLoader.ts
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
import {environment} from "~/core/globals";
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
import type {SettingsJsonFile} from "~/types/SettingsJsonFile";
|
||||||
|
import * as https from "node:https";
|
||||||
|
|
||||||
|
export function loadJsonFile(){
|
||||||
|
try {
|
||||||
|
console.log(environment.files.settings)
|
||||||
|
createEmptySettingsIfNotExists()
|
||||||
|
let jsonStr = fs.readFileSync(environment.files.settings, 'utf-8')
|
||||||
|
environment.settings = JSON.parse(jsonStr);
|
||||||
|
|
||||||
|
checkAllServerDirectories()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error reading or parsing JSON file:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function saveJsonFile() {
|
||||||
|
const jsonSettings = JSON.stringify(environment.settings, null, 2);
|
||||||
|
fs.writeFileSync(environment.files.settings, jsonSettings);
|
||||||
|
console.log(`Settings saved to ` + environment.files.settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export function removeServerByName(name: string) {
|
||||||
|
const index = environment.settings.servers.findIndex(item => item.name === name);
|
||||||
|
environment.settings.servers.splice(index, 1);
|
||||||
|
console.log("Removed server \"" + name + "\" at index " + index);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function createEmptySettingsIfNotExists() {
|
||||||
|
try {
|
||||||
|
fs.accessSync(environment.files.settings);
|
||||||
|
console.log('Settings already exists!');
|
||||||
|
} catch (error) {
|
||||||
|
console.log('Settings does not exist, creating an empty Settings-file...');
|
||||||
|
let emptySettings: SettingsJsonFile = ({
|
||||||
|
eula: false,
|
||||||
|
servers:[]
|
||||||
|
});
|
||||||
|
const jsonSettings = JSON.stringify(emptySettings, null, 2);
|
||||||
|
fs.writeFileSync(environment.files.settings, jsonSettings);
|
||||||
|
console.log('File created successfully and is empty!');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function createPathIfNotExists(dirPath: string) {
|
||||||
|
try {
|
||||||
|
if (!fs.existsSync(dirPath)) {
|
||||||
|
fs.mkdirSync(dirPath, { recursive: true }); // Create the directory (and parent directories if necessary)
|
||||||
|
console.log(`Directory created at ${dirPath}`);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error creating directory:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function checkAllServerDirectories(){
|
||||||
|
environment.settings.servers.forEach(server => {
|
||||||
|
createPathIfNotExists(environment.paths.servers + "/" + server.name)
|
||||||
|
})
|
||||||
|
|
||||||
|
environment.settings.servers.forEach(server => {
|
||||||
|
const destinationPath = environment.paths.servers + "/" + server.name + "/server.jar"
|
||||||
|
try {
|
||||||
|
fs.accessSync(destinationPath);
|
||||||
|
console.log('Settings already exists!');
|
||||||
|
} catch (error) {
|
||||||
|
downloadFile(server.jar_url, destinationPath);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function downloadFile(url: string, dest: string) {
|
||||||
|
if(url == "")
|
||||||
|
return;
|
||||||
|
|
||||||
|
|
||||||
|
console.log(dest + ' doesnt exist, downloading now from ' + url);
|
||||||
|
const file = fs.createWriteStream(dest);
|
||||||
|
|
||||||
|
https.get(url, (response) => {
|
||||||
|
if (response.statusCode === 200) {
|
||||||
|
response.pipe(file);
|
||||||
|
} else {
|
||||||
|
console.error(`Failed to download file. Status Code: ${response.statusCode}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
file.on('finish', () => {
|
||||||
|
file.close();
|
||||||
|
console.log('Download complete!');
|
||||||
|
});
|
||||||
|
}).on('error', (err) => {
|
||||||
|
// Handle any errors during the download
|
||||||
|
console.error('Error during download:', err.message);
|
||||||
|
fs.unlink(dest, () => {});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue